mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-03 18:53:17 -05:00 
			
		
		
		
	chore: frontend testing setup (#1739)
* add vitest * initialize lib w/ tests * move to dev dep * run tests in CI * update file names * move api folder to lib * move api and api types to same folder * update generator outpath * rm husky * i guess i _did_ need those types * reorg types * extract validators into testable components * (WIP) start composable testing * fix import type * fix linter complaint * simplify icon type def * fix linter errors (maybe?) * rename client file for sorting
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/partial-frontend.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/partial-frontend.yml
									
									
									
									
										vendored
									
									
								
							@@ -38,6 +38,10 @@ jobs:
 | 
				
			|||||||
        run: yarn lint
 | 
					        run: yarn lint
 | 
				
			||||||
        working-directory: "frontend"
 | 
					        working-directory: "frontend"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Run tests 🧪
 | 
				
			||||||
 | 
					        run: yarn test:ci
 | 
				
			||||||
 | 
					        working-directory: "frontend"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  build:
 | 
					  build:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -55,7 +55,6 @@ develop-eggs/
 | 
				
			|||||||
downloads/
 | 
					downloads/
 | 
				
			||||||
eggs/
 | 
					eggs/
 | 
				
			||||||
.eggs/
 | 
					.eggs/
 | 
				
			||||||
lib/
 | 
					 | 
				
			||||||
lib64/
 | 
					lib64/
 | 
				
			||||||
parts/
 | 
					parts/
 | 
				
			||||||
!frontend/src/components/Recipe/Parts/
 | 
					!frontend/src/components/Recipe/Parts/
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,7 +75,7 @@ def generate_typescript_types() -> None:
 | 
				
			|||||||
        return str_path
 | 
					        return str_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    schema_path = PROJECT_DIR / "mealie" / "schema"
 | 
					    schema_path = PROJECT_DIR / "mealie" / "schema"
 | 
				
			||||||
    types_dir = PROJECT_DIR / "frontend" / "types" / "api-types"
 | 
					    types_dir = PROJECT_DIR / "frontend" / "lib" / "api" / "types"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ignore_dirs = ["__pycache__", "static", "_mealie"]
 | 
					    ignore_dirs = ["__pycache__", "static", "_mealie"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								frontend/.husky/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								frontend/.husky/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1 +0,0 @@
 | 
				
			|||||||
_
 | 
					 | 
				
			||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { parseISO, formatDistanceToNow } from "date-fns";
 | 
					import { parseISO, formatDistanceToNow } from "date-fns";
 | 
				
			||||||
import { GroupDataExport } from "~/types/api-types/group";
 | 
					import { GroupDataExport } from "~/lib/api/types/group";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    exports: {
 | 
					    exports: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
 | 
					import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
 | 
				
			||||||
import { RecipeTag, RecipeCategory } from "~/types/api-types/group";
 | 
					import { RecipeTag, RecipeCategory } from "~/lib/api/types/group";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { ReadWebhook } from "~/types/api-types/group";
 | 
					import { ReadWebhook } from "~/lib/api/types/group";
 | 
				
			||||||
import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
 | 
					import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,7 @@
 | 
				
			|||||||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
 | 
					import RecipeContextMenu from "./RecipeContextMenu.vue";
 | 
				
			||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
 | 
					import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SAVE_EVENT = "save";
 | 
					const SAVE_EVENT = "save";
 | 
				
			||||||
const DELETE_EVENT = "delete";
 | 
					const DELETE_EVENT = "delete";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,7 +82,7 @@ import { defineComponent, reactive, useContext } from "@nuxtjs/composition-api";
 | 
				
			|||||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
 | 
					import { useStaticRoutes, useUserApi } from "~/composables/api";
 | 
				
			||||||
import { alert } from "~/composables/use-toast";
 | 
					import { alert } from "~/composables/use-toast";
 | 
				
			||||||
import { detectServerBaseUrl } from "~/composables/use-utils";
 | 
					import { detectServerBaseUrl } from "~/composables/use-utils";
 | 
				
			||||||
import { RecipeAsset } from "~/types/api-types/recipe";
 | 
					import { RecipeAsset } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,7 +129,7 @@ import RecipeCard from "./RecipeCard.vue";
 | 
				
			|||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
 | 
					import RecipeCardMobile from "./RecipeCardMobile.vue";
 | 
				
			||||||
import { useAsyncKey } from "~/composables/use-utils";
 | 
					import { useAsyncKey } from "~/composables/use-utils";
 | 
				
			||||||
import { useLazyRecipes } from "~/composables/recipes";
 | 
					import { useLazyRecipes } from "~/composables/recipes";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useUserSortPreferences } from "~/composables/use-users/preferences";
 | 
					import { useUserSortPreferences } from "~/composables/use-users/preferences";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const REPLACE_RECIPES_EVENT = "replaceRecipes";
 | 
					const REPLACE_RECIPES_EVENT = "replaceRecipes";
 | 
				
			||||||
@@ -230,13 +230,13 @@ export default defineComponent({
 | 
				
			|||||||
        page.value,
 | 
					        page.value,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // we double-up the first call to avoid a bug with large screens that render the entire first page without scrolling, preventing additional loading
 | 
					        // we double-up the first call to avoid a bug with large screens that render the entire first page without scrolling, preventing additional loading
 | 
				
			||||||
        perPage.value*2,
 | 
					        perPage.value * 2,
 | 
				
			||||||
        preferences.value.orderBy,
 | 
					        preferences.value.orderBy,
 | 
				
			||||||
        preferences.value.orderDirection,
 | 
					        preferences.value.orderDirection,
 | 
				
			||||||
        cookbook.value,
 | 
					        cookbook.value,
 | 
				
			||||||
        category.value,
 | 
					        category.value,
 | 
				
			||||||
        tag.value,
 | 
					        tag.value,
 | 
				
			||||||
        tool.value,
 | 
					        tool.value
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // since we doubled the first call, we also need to advance the page
 | 
					      // since we doubled the first call, we also need to advance the page
 | 
				
			||||||
@@ -263,7 +263,7 @@ export default defineComponent({
 | 
				
			|||||||
          cookbook.value,
 | 
					          cookbook.value,
 | 
				
			||||||
          category.value,
 | 
					          category.value,
 | 
				
			||||||
          tag.value,
 | 
					          tag.value,
 | 
				
			||||||
          tool.value,
 | 
					          tool.value
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        if (!newRecipes.length) {
 | 
					        if (!newRecipes.length) {
 | 
				
			||||||
          hasMore.value = false;
 | 
					          hasMore.value = false;
 | 
				
			||||||
@@ -325,7 +325,7 @@ export default defineComponent({
 | 
				
			|||||||
          cookbook.value,
 | 
					          cookbook.value,
 | 
				
			||||||
          category.value,
 | 
					          category.value,
 | 
				
			||||||
          tag.value,
 | 
					          tag.value,
 | 
				
			||||||
          tool.value,
 | 
					          tool.value
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        context.emit(REPLACE_RECIPES_EVENT, newRecipes);
 | 
					        context.emit(REPLACE_RECIPES_EVENT, newRecipes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeCategory, RecipeTag, RecipeTool } from "~/types/api-types/user";
 | 
					import { RecipeCategory, RecipeTag, RecipeTool } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type UrlPrefixParam = "tags" | "categories" | "tools";
 | 
					export type UrlPrefixParam = "tags" | "categories" | "tools";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,7 +56,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { RecipeCommentOut } from "~/types/api-types/recipe";
 | 
					import { RecipeCommentOut } from "~/lib/api/types/recipe";
 | 
				
			||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
 | 
					import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -101,8 +101,8 @@ import RecipeDialogShare from "./RecipeDialogShare.vue";
 | 
				
			|||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { alert } from "~/composables/use-toast";
 | 
					import { alert } from "~/composables/use-toast";
 | 
				
			||||||
import { planTypeOptions } from "~/composables/use-group-mealplan";
 | 
					import { planTypeOptions } from "~/composables/use-group-mealplan";
 | 
				
			||||||
import { ShoppingListSummary } from "~/types/api-types/group";
 | 
					import { ShoppingListSummary } from "~/lib/api/types/group";
 | 
				
			||||||
import { PlanEntryType } from "~/types/api-types/meal-plan";
 | 
					import { PlanEntryType } from "~/lib/api/types/meal-plan";
 | 
				
			||||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
 | 
					import { useAxiosDownloader } from "~/composables/api/use-axios-download";
 | 
				
			||||||
import { useCopy } from "~/composables/use-copy";
 | 
					import { useCopy } from "~/composables/use-copy";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,9 +44,9 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeChip from "./RecipeChips.vue";
 | 
					import RecipeChip from "./RecipeChips.vue";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { UserOut } from "~/types/api-types/user";
 | 
					import { UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const INPUT_EVENT = "input";
 | 
					const INPUT_EVENT = "input";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@
 | 
				
			|||||||
import { defineComponent, toRefs, reactive, ref, watch, useRoute } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, toRefs, reactive, ref, watch, useRoute } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
 | 
					import RecipeCardMobile from "./RecipeCardMobile.vue";
 | 
				
			||||||
import { useRecipes, allRecipes, useRecipeSearch } from "~/composables/recipes";
 | 
					import { useRecipes, allRecipes, useRecipeSearch } from "~/composables/recipes";
 | 
				
			||||||
import { RecipeSummary } from "~/types/api-types/recipe";
 | 
					import { RecipeSummary } from "~/lib/api/types/recipe";
 | 
				
			||||||
const SELECTED_EVENT = "selected";
 | 
					const SELECTED_EVENT = "selected";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, computed, toRefs, reactive, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed, toRefs, reactive, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useClipboard, useShare, whenever } from "@vueuse/core";
 | 
					import { useClipboard, useShare, whenever } from "@vueuse/core";
 | 
				
			||||||
import { RecipeShareToken } from "~/types/api-types/recipe";
 | 
					import { RecipeShareToken } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { alert } from "~/composables/use-toast";
 | 
					import { alert } from "~/composables/use-toast";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { UserOut } from "~/types/api-types/user";
 | 
					import { UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    slug: {
 | 
					    slug: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -131,7 +131,7 @@
 | 
				
			|||||||
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
 | 
					import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
 | 
				
			||||||
import { validators } from "~/composables/use-validators";
 | 
					import { validators } from "~/composables/use-validators";
 | 
				
			||||||
import { RecipeIngredient } from "~/types/api-types/recipe";
 | 
					import { RecipeIngredient } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@
 | 
				
			|||||||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
 | 
				
			||||||
// @ts-ignore vue-markdown has no types
 | 
					// @ts-ignore vue-markdown has no types
 | 
				
			||||||
import { parseIngredientText } from "~/composables/recipes";
 | 
					import { parseIngredientText } from "~/composables/recipes";
 | 
				
			||||||
import { RecipeIngredient } from "~/types/api-types/recipe";
 | 
					import { RecipeIngredient } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {},
 | 
					  components: {},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -230,7 +230,7 @@ import {
 | 
				
			|||||||
  useContext,
 | 
					  useContext,
 | 
				
			||||||
  computed,
 | 
					  computed,
 | 
				
			||||||
} from "@nuxtjs/composition-api";
 | 
					} from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset } from "~/types/api-types/recipe";
 | 
					import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { parseIngredientText } from "~/composables/recipes";
 | 
					import { parseIngredientText } from "~/composables/recipes";
 | 
				
			||||||
import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
 | 
					import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
 | 
				
			||||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
					import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeSummary } from "~/types/api-types/recipe";
 | 
					import { RecipeSummary } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeNote } from "~/types/api-types/recipe";
 | 
					import { RecipeNote } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { Nutrition } from "~/types/api-types/recipe";
 | 
					import { Nutrition } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -146,9 +146,9 @@ import { until } from "@vueuse/core";
 | 
				
			|||||||
import { invoke } from "@vueuse/shared";
 | 
					import { invoke } from "@vueuse/shared";
 | 
				
			||||||
import draggable from "vuedraggable";
 | 
					import draggable from "vuedraggable";
 | 
				
			||||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
					import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
				
			||||||
import { OcrTsvResponse as NullableOcrTsvResponse } from "~/types/api-types/ocr";
 | 
					import { OcrTsvResponse as NullableOcrTsvResponse } from "~/lib/api/types/ocr";
 | 
				
			||||||
import { validators } from "~/composables/use-validators";
 | 
					import { validators } from "~/composables/use-validators";
 | 
				
			||||||
import { Recipe, RecipeIngredient, RecipeStep } from "~/types/api-types/recipe";
 | 
					import { Recipe, RecipeIngredient, RecipeStep } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { Paths, Leaves, SelectedRecipeLeaves } from "~/types/ocr-types";
 | 
					import { Paths, Leaves, SelectedRecipeLeaves } from "~/types/ocr-types";
 | 
				
			||||||
import BannerExperimental from "~/components/global/BannerExperimental.vue";
 | 
					import BannerExperimental from "~/components/global/BannerExperimental.vue";
 | 
				
			||||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
 | 
					import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
 | 
				
			||||||
@@ -157,7 +157,7 @@ import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientE
 | 
				
			|||||||
import RecipeOcrEditorPageCanvas from "~/components/Domain/Recipe/RecipeOcrEditorPage/RecipeOcrEditorPageParts/RecipeOcrEditorPageCanvas.vue";
 | 
					import RecipeOcrEditorPageCanvas from "~/components/Domain/Recipe/RecipeOcrEditorPage/RecipeOcrEditorPageParts/RecipeOcrEditorPageCanvas.vue";
 | 
				
			||||||
import RecipeOcrEditorPageHelp from "~/components/Domain/Recipe/RecipeOcrEditorPage/RecipeOcrEditorPageParts/RecipeOcrEditorPageHelp.vue";
 | 
					import RecipeOcrEditorPageHelp from "~/components/Domain/Recipe/RecipeOcrEditorPage/RecipeOcrEditorPageParts/RecipeOcrEditorPageHelp.vue";
 | 
				
			||||||
import { uuid4 } from "~/composables/use-utils";
 | 
					import { uuid4 } from "~/composables/use-utils";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Temporary Shim until we have a better solution
 | 
					// Temporary Shim until we have a better solution
 | 
				
			||||||
// https://github.com/phillipdupuis/pydantic-to-typescript/issues/28
 | 
					// https://github.com/phillipdupuis/pydantic-to-typescript/issues/28
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,8 +41,8 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, reactive, useContext, ref, toRefs, watch } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, reactive, useContext, ref, toRefs, watch } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { onMounted } from "vue-demi";
 | 
					import { onMounted } from "vue-demi";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { OcrTsvResponse as NullableOcrTsvResponse } from "~/types/api-types/ocr";
 | 
					import { OcrTsvResponse as NullableOcrTsvResponse } from "~/lib/api/types/ocr";
 | 
				
			||||||
import { CanvasModes, SelectedTextSplitModes, ImagePosition, Mouse, CanvasRect, ToolbarIcons } from "~/types/ocr-types";
 | 
					import { CanvasModes, SelectedTextSplitModes, ImagePosition, Mouse, CanvasRect, ToolbarIcons } from "~/types/ocr-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Temporary Shim until we have a better solution
 | 
					// Temporary Shim until we have a better solution
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,7 @@
 | 
				
			|||||||
import { computed, defineComponent, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
 | 
					import { useCategoryStore, useTagStore, useToolStore } from "~/composables/store";
 | 
				
			||||||
import { RecipeOrganizer, Organizer } from "~/types/recipe/organizers";
 | 
					import { RecipeOrganizer, Organizer } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CREATED_ITEM_EVENT = "created-item";
 | 
					const CREATED_ITEM_EVENT = "created-item";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@
 | 
				
			|||||||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useContextPresets } from "~/composables/use-context-presents";
 | 
					import { useContextPresets } from "~/composables/use-context-presents";
 | 
				
			||||||
import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue";
 | 
					import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue";
 | 
				
			||||||
import { RecipeOrganizer } from "~/types/recipe/organizers";
 | 
					import { RecipeOrganizer } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface GenericItem {
 | 
					interface GenericItem {
 | 
				
			||||||
  id?: string;
 | 
					  id?: string;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,11 +42,11 @@
 | 
				
			|||||||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { computed, onMounted } from "vue-demi";
 | 
					import { computed, onMounted } from "vue-demi";
 | 
				
			||||||
import RecipeOrganizerDialog from "./RecipeOrganizerDialog.vue";
 | 
					import RecipeOrganizerDialog from "./RecipeOrganizerDialog.vue";
 | 
				
			||||||
import { RecipeCategory, RecipeTag } from "~/types/api-types/user";
 | 
					import { RecipeCategory, RecipeTag } from "~/lib/api/types/user";
 | 
				
			||||||
import { RecipeTool } from "~/types/api-types/admin";
 | 
					import { RecipeTool } from "~/lib/api/types/admin";
 | 
				
			||||||
import { useTagStore } from "~/composables/store/use-tag-store";
 | 
					import { useTagStore } from "~/composables/store/use-tag-store";
 | 
				
			||||||
import { useCategoryStore, useToolStore } from "~/composables/store";
 | 
					import { useCategoryStore, useToolStore } from "~/composables/store";
 | 
				
			||||||
import { Organizer, RecipeOrganizer } from "~/types/recipe/organizers";
 | 
					import { Organizer, RecipeOrganizer } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,8 +90,8 @@ import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue
 | 
				
			|||||||
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
 | 
					import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
 | 
				
			||||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
 | 
					import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
 | 
				
			||||||
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useRecipeMeta } from "~/composables/recipes";
 | 
					import { useRecipeMeta } from "~/composables/recipes";
 | 
				
			||||||
import { useRouteQuery } from "~/composables/use-router";
 | 
					import { useRouteQuery } from "~/composables/use-router";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,9 +56,9 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { Recipe, RecipeCommentOut } from "~/types/api-types/recipe";
 | 
					import { Recipe, RecipeCommentOut } from "~/lib/api/types/recipe";
 | 
				
			||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
 | 
					import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,8 +13,8 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, onUnmounted } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, onUnmounted } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { clearPageState, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { clearPageState, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
 | 
					import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
 | 
				
			||||||
import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
 | 
					import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,8 +57,8 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    recipe: {
 | 
					    recipe: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,8 +61,8 @@ import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
 | 
				
			|||||||
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
 | 
					import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
 | 
				
			||||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
					import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
				
			||||||
import { useStaticRoutes } from "~/composables/api";
 | 
					import { useStaticRoutes } from "~/composables/api";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { usePageState, usePageUser, PageMode, EditorMode } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState, usePageUser, PageMode, EditorMode } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,8 +55,8 @@
 | 
				
			|||||||
import draggable from "vuedraggable";
 | 
					import draggable from "vuedraggable";
 | 
				
			||||||
import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
 | 
					import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
 | 
				
			||||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
 | 
					import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
 | 
				
			||||||
import { uuid4 } from "~/composables/use-utils";
 | 
					import { uuid4 } from "~/composables/use-utils";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,8 +28,8 @@
 | 
				
			|||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { useToolStore } from "~/composables/store";
 | 
					import { useToolStore } from "~/composables/store";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
 | 
					import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -225,12 +225,12 @@ import {
 | 
				
			|||||||
  computed,
 | 
					  computed,
 | 
				
			||||||
} from "@nuxtjs/composition-api";
 | 
					} from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeIngredientHtml from "../../RecipeIngredientHtml.vue";
 | 
					import RecipeIngredientHtml from "../../RecipeIngredientHtml.vue";
 | 
				
			||||||
import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset, Recipe } from "~/types/api-types/recipe";
 | 
					import { RecipeStep, IngredientReferences, RecipeIngredient, RecipeAsset, Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { parseIngredientText } from "~/composables/recipes";
 | 
					import { parseIngredientText } from "~/composables/recipes";
 | 
				
			||||||
import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
 | 
					import { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
 | 
				
			||||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
					import { useUserApi, useStaticRoutes } from "~/composables/api";
 | 
				
			||||||
import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import DropZone from "~/components/global/DropZone.vue";
 | 
					import DropZone from "~/components/global/DropZone.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface MergerHistory {
 | 
					interface MergerHistory {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,8 +59,8 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import RecipeOrganizerSelector from "@/components/Domain/Recipe/RecipeOrganizerSelector.vue";
 | 
					import RecipeOrganizerSelector from "@/components/Domain/Recipe/RecipeOrganizerSelector.vue";
 | 
				
			||||||
import RecipeNutrition from "~/components/Domain/Recipe/RecipeNutrition.vue";
 | 
					import RecipeNutrition from "~/components/Domain/Recipe/RecipeNutrition.vue";
 | 
				
			||||||
import RecipeChips from "@/components/Domain/Recipe/RecipeChips.vue";
 | 
					import RecipeChips from "@/components/Domain/Recipe/RecipeChips.vue";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,8 +30,8 @@
 | 
				
			|||||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
 | 
					import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
 | 
				
			||||||
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
 | 
					import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,8 +44,8 @@
 | 
				
			|||||||
import { defineComponent } from "@nuxtjs/composition-api";
 | 
					import { defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
					import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
 | 
				
			||||||
import { validators } from "~/composables/use-validators";
 | 
					import { validators } from "~/composables/use-validators";
 | 
				
			||||||
import { NoUndefinedField } from "~/types/api";
 | 
					import { NoUndefinedField } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
 | 
					import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
 | 
				
			||||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
					import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,9 +23,12 @@
 | 
				
			|||||||
        class="print-section"
 | 
					        class="print-section"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <h4 v-if="ingredientSection.ingredients[0].title" class="ingredient-title mt-2">
 | 
					        <h4 v-if="ingredientSection.ingredients[0].title" class="ingredient-title mt-2">
 | 
				
			||||||
            {{ ingredientSection.ingredients[0].title }}
 | 
					          {{ ingredientSection.ingredients[0].title }}
 | 
				
			||||||
        </h4>
 | 
					        </h4>
 | 
				
			||||||
        <div class="ingredient-grid" :style="{gridTemplateRows:`repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)`}">
 | 
					        <div
 | 
				
			||||||
 | 
					          class="ingredient-grid"
 | 
				
			||||||
 | 
					          :style="{ gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          <template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
 | 
					          <template v-for="(ingredient, ingredientIndex) in ingredientSection.ingredients">
 | 
				
			||||||
            <p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
 | 
					            <p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
@@ -70,7 +73,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
				
			||||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
					import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
 | 
				
			||||||
import { Recipe, RecipeIngredient, RecipeStep } from "~/types/api-types/recipe";
 | 
					import { Recipe, RecipeIngredient, RecipeStep } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { parseIngredientText } from "~/composables/recipes";
 | 
					import { parseIngredientText } from "~/composables/recipes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type IngredientSection = {
 | 
					type IngredientSection = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeSettings } from "~/types/api-types/recipe";
 | 
					import { RecipeSettings } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, ref, computed } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, ref, computed } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RecipeTool } from "~/types/api-types/recipe";
 | 
					import { RecipeTool } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { useTools } from "~/composables/recipes";
 | 
					import { useTools } from "~/composables/recipes";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
 | 
					import { computed, defineComponent } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { MultiPurposeLabelSummary } from "~/types/api-types/recipe";
 | 
					import { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,9 +55,9 @@
 | 
				
			|||||||
import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
 | 
					import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
 | 
				
			||||||
import MultiPurposeLabel from "./MultiPurposeLabel.vue";
 | 
					import MultiPurposeLabel from "./MultiPurposeLabel.vue";
 | 
				
			||||||
import { ShoppingListItemCreate } from "~/types/api-types/group";
 | 
					import { ShoppingListItemCreate } from "~/lib/api/types/group";
 | 
				
			||||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
 | 
					import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
 | 
				
			||||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
 | 
					import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
 | 
				
			||||||
import { getDisplayText } from "~/composables/use-display-text";
 | 
					import { getDisplayText } from "~/composables/use-display-text";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface actions {
 | 
					interface actions {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,9 +96,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/types/api-types/group";
 | 
					import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
 | 
				
			||||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
 | 
					import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
 | 
				
			||||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
 | 
					import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { UserOut } from "~/types/api-types/user";
 | 
					import { UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
@@ -35,7 +35,7 @@ export default defineComponent({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const imageURL = computed(() => {
 | 
					    const imageURL = computed(() => {
 | 
				
			||||||
      // TODO Setup correct user type for $auth.user
 | 
					      // TODO Setup correct user type for $auth.user
 | 
				
			||||||
      const user = $auth.user as unknown as (UserOut | null);
 | 
					      const user = $auth.user as unknown as UserOut | null;
 | 
				
			||||||
      const key = user?.cacheKey ?? "";
 | 
					      const key = user?.cacheKey ?? "";
 | 
				
			||||||
      return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
 | 
					      return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,8 +32,8 @@
 | 
				
			|||||||
 * using the .sync syntax `item-id.sync="item.labelId"`
 | 
					 * using the .sync syntax `item-id.sync="item.labelId"`
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, computed } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
 | 
					import { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
 | 
				
			||||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
 | 
					import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,4 +27,3 @@ export default defineComponent({
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import { defineComponent, useContext, useRouter } from "@nuxtjs/composition-api";
 | 
					import { defineComponent, useContext, useRouter } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { ReportSummary } from "~/types/api-types/reports";
 | 
					import { ReportSummary } from "~/lib/api/types/reports";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineComponent({
 | 
					export default defineComponent({
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
import { AxiosResponse } from "axios";
 | 
					import { AxiosResponse } from "axios";
 | 
				
			||||||
import { useContext } from "@nuxtjs/composition-api";
 | 
					import { useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import type { NuxtAxiosInstance } from "@nuxtjs/axios";
 | 
					import type { NuxtAxiosInstance } from "@nuxtjs/axios";
 | 
				
			||||||
import { AdminAPI, Api } from "~/api";
 | 
					import { ApiRequestInstance, RequestResponse } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { ApiRequestInstance, RequestResponse } from "~/types/api";
 | 
					import { AdminAPI, PublicApi, UserApi } from "~/lib/api";
 | 
				
			||||||
import { PublicApi } from "~/api/public-api";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const request = {
 | 
					const request = {
 | 
				
			||||||
  async safe<T, U>(
 | 
					  async safe<T, U>(
 | 
				
			||||||
@@ -66,12 +65,12 @@ export const useAdminApi = function (): AdminAPI {
 | 
				
			|||||||
  return new AdminAPI(requests);
 | 
					  return new AdminAPI(requests);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useUserApi = function (): Api {
 | 
					export const useUserApi = function (): UserApi {
 | 
				
			||||||
  const { $axios, i18n } = useContext();
 | 
					  const { $axios, i18n } = useContext();
 | 
				
			||||||
  $axios.setHeader("Accept-Language", i18n.locale);
 | 
					  $axios.setHeader("Accept-Language", i18n.locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const requests = getRequests($axios);
 | 
					  const requests = getRequests($axios);
 | 
				
			||||||
  return new Api(requests);
 | 
					  return new UserApi(requests);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const usePublicApi = function (): PublicApi {
 | 
					export const usePublicApi = function (): PublicApi {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { ref, Ref, useAsync, useContext } from "@nuxtjs/composition-api";
 | 
					import { ref, Ref, useAsync, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "../use-utils";
 | 
					import { useAsyncKey } from "../use-utils";
 | 
				
			||||||
import { AppInfo } from "~/types/api-types/admin";
 | 
					import { AppInfo } from "~/lib/api/types/admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useAppInfo(): Ref<AppInfo | null> {
 | 
					export function useAppInfo(): Ref<AppInfo | null> {
 | 
				
			||||||
  const appInfo = ref<null | AppInfo>(null);
 | 
					  const appInfo = ref<null | AppInfo>(null);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Ref, useAsync } from "@nuxtjs/composition-api";
 | 
					import { Ref, useAsync } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "../use-utils";
 | 
					import { useAsyncKey } from "../use-utils";
 | 
				
			||||||
import { BaseCRUDAPI } from "~/api/_base";
 | 
					import { BaseCRUDAPI } from "~/lib/api/base/base-clients";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type BoundT = {
 | 
					type BoundT = {
 | 
				
			||||||
  id?: string | number;
 | 
					  id?: string | number;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { UserOut } from "~/types/api-types/user";
 | 
					import { UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum PageMode {
 | 
					export enum PageMode {
 | 
				
			||||||
  EDIT = "EDIT",
 | 
					  EDIT = "EDIT",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import DOMPurify from "isomorphic-dompurify";
 | 
					import DOMPurify from "isomorphic-dompurify";
 | 
				
			||||||
import { useFraction } from "./use-fraction";
 | 
					import { useFraction } from "./use-fraction";
 | 
				
			||||||
import { RecipeIngredient } from "~/types/api-types/recipe";
 | 
					import { RecipeIngredient } from "~/lib/api/types/recipe";
 | 
				
			||||||
const { frac } = useFraction();
 | 
					const { frac } = useFraction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function sanitizeIngredientHTML(rawHtml: string) {
 | 
					function sanitizeIngredientHTML(rawHtml: string) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Ref } from "@nuxtjs/composition-api";
 | 
					import { Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStaticRoutes } from "~/composables/api";
 | 
					import { useStaticRoutes } from "~/composables/api";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface RecipeMeta {
 | 
					export interface RecipeMeta {
 | 
				
			||||||
  title?: string;
 | 
					  title?: string;
 | 
				
			||||||
@@ -53,6 +53,6 @@ export const useRecipeMeta = () => {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
  return { recipeMeta };
 | 
					  return { recipeMeta };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { computed, reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { computed, reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import Fuse from "fuse.js";
 | 
					import Fuse from "fuse.js";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useRecipeSearch = (recipes: Ref<Recipe[] | null>) => {
 | 
					export const useRecipeSearch = (recipes: Ref<Recipe[] | null>) => {
 | 
				
			||||||
  const localState = reactive({
 | 
					  const localState = reactive({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
 | 
				
			|||||||
import { useAsyncKey } from "../use-utils";
 | 
					import { useAsyncKey } from "../use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { VForm } from "~/types/vuetify";
 | 
					import { VForm } from "~/types/vuetify";
 | 
				
			||||||
import { RecipeTool } from "~/types/api-types/user";
 | 
					import { RecipeTool } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useTools = function (eager = true) {
 | 
					export const useTools = function (eager = true) {
 | 
				
			||||||
  const workingToolData = reactive<RecipeTool>({
 | 
					  const workingToolData = reactive<RecipeTool>({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { ref, onMounted } from "@nuxtjs/composition-api";
 | 
					import { ref, onMounted } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useRecipe = function (slug: string, eager = true) {
 | 
					export const useRecipe = function (slug: string, eager = true) {
 | 
				
			||||||
  const api = useUserApi();
 | 
					  const api = useUserApi();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "../use-utils";
 | 
					import { useAsyncKey } from "../use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const allRecipes = ref<Recipe[]>([]);
 | 
					export const allRecipes = ref<Recipe[]>([]);
 | 
				
			||||||
export const recentRecipes = ref<Recipe[]>([]);
 | 
					export const recentRecipes = ref<Recipe[]>([]);
 | 
				
			||||||
@@ -21,7 +21,14 @@ export const useLazyRecipes = function () {
 | 
				
			|||||||
    tag: string | null = null,
 | 
					    tag: string | null = null,
 | 
				
			||||||
    tool: string | null = null
 | 
					    tool: string | null = null
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    const { data } = await api.recipes.getAll(page, perPage, { orderBy, orderDirection, cookbook, "categories": category, "tags": tag, "tools": tool });
 | 
					    const { data } = await api.recipes.getAll(page, perPage, {
 | 
				
			||||||
 | 
					      orderBy,
 | 
				
			||||||
 | 
					      orderDirection,
 | 
				
			||||||
 | 
					      cookbook,
 | 
				
			||||||
 | 
					      categories: category,
 | 
				
			||||||
 | 
					      tags: tag,
 | 
				
			||||||
 | 
					      tools: tool,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    return data ? data.items : [];
 | 
					    return data ? data.items : [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,7 +61,7 @@ export const useLazyRecipes = function () {
 | 
				
			|||||||
    appendRecipes,
 | 
					    appendRecipes,
 | 
				
			||||||
    assignSorted,
 | 
					    assignSorted,
 | 
				
			||||||
    removeRecipe,
 | 
					    removeRecipe,
 | 
				
			||||||
    replaceRecipes
 | 
					    replaceRecipes,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { RecipeCategory } from "~/types/api-types/admin";
 | 
					import { RecipeCategory } from "~/lib/api/types/admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const categoryStore: Ref<RecipeCategory[]> = ref([]);
 | 
					const categoryStore: Ref<RecipeCategory[]> = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { ref, reactive, Ref } from "@nuxtjs/composition-api";
 | 
					import { ref, reactive, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { IngredientFood } from "~/types/api-types/recipe";
 | 
					import { IngredientFood } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let foodStore: Ref<IngredientFood[] | null> | null = null;
 | 
					let foodStore: Ref<IngredientFood[] | null> | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
 | 
					import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let labelStore: Ref<MultiPurposeLabelOut[] | null> | null = null;
 | 
					let labelStore: Ref<MultiPurposeLabelOut[] | null> | null = null;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { RecipeTag } from "~/types/api-types/admin";
 | 
					import { RecipeTag } from "~/lib/api/types/admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const items: Ref<RecipeTag[]> = ref([]);
 | 
					const items: Ref<RecipeTag[]> = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { reactive, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { RecipeTool } from "~/types/api-types/recipe";
 | 
					import { RecipeTool } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toolStore: Ref<RecipeTool[]> = ref([]);
 | 
					const toolStore: Ref<RecipeTool[]> = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { ref, reactive, Ref } from "@nuxtjs/composition-api";
 | 
					import { ref, reactive, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useStoreActions } from "../partials/use-actions-factory";
 | 
					import { useStoreActions } from "../partials/use-actions-factory";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { IngredientUnit } from "~/types/api-types/recipe";
 | 
					import { IngredientUnit } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let unitStore: Ref<IngredientUnit[] | null> | null = null;
 | 
					let unitStore: Ref<IngredientUnit[] | null> | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { toastLoading, loader } from "./use-toast";
 | 
					import { toastLoading, loader } from "./use-toast";
 | 
				
			||||||
import { AllBackups, BackupOptions } from "~/types/api-types/admin";
 | 
					import { AllBackups, BackupOptions } from "~/lib/api/types/admin";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ImportBackup {
 | 
					interface ImportBackup {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
 * with the food, units, quantity, and notes.
 | 
					 * with the food, units, quantity, and notes.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
 | 
					import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getDisplayText(
 | 
					export function getDisplayText(
 | 
				
			||||||
  notes = "",
 | 
					  notes = "",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { useAsync, ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "./use-utils";
 | 
					import { useAsyncKey } from "./use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { ReadCookBook, UpdateCookBook } from "~/types/api-types/cookbook";
 | 
					import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let cookbookStore: Ref<ReadCookBook[] | null> | null = null;
 | 
					let cookbookStore: Ref<ReadCookBook[] | null> | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import { useAsync, ref, Ref, watch } from "@nuxtjs/composition-api";
 | 
				
			|||||||
import { format } from "date-fns";
 | 
					import { format } from "date-fns";
 | 
				
			||||||
import { useAsyncKey } from "./use-utils";
 | 
					import { useAsyncKey } from "./use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { CreatePlanEntry, PlanEntryType, UpdatePlanEntry } from "~/types/api-types/meal-plan";
 | 
					import { CreatePlanEntry, PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const planTypeOptions = [
 | 
					export const planTypeOptions = [
 | 
				
			||||||
  { text: "Breakfast", value: "breakfast" },
 | 
					  { text: "Breakfast", value: "breakfast" },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "./use-utils";
 | 
					import { useAsyncKey } from "./use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { ReadWebhook } from "~/types/api-types/group";
 | 
					import { ReadWebhook } from "~/lib/api/types/group";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useGroupWebhooks = function () {
 | 
					export const useGroupWebhooks = function () {
 | 
				
			||||||
  const api = useUserApi();
 | 
					  const api = useUserApi();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useAsyncKey } from "./use-utils";
 | 
					import { useAsyncKey } from "./use-utils";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { GroupBase } from "~/types/api-types/user";
 | 
					import { GroupBase } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useGroupSelf = function () {
 | 
					export const useGroupSelf = function () {
 | 
				
			||||||
  const api = useUserApi();
 | 
					  const api = useUserApi();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								frontend/composables/use-passwords.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/composables/use-passwords.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import { ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
 | 
					import { describe, expect, test } from "vitest";
 | 
				
			||||||
 | 
					import { usePasswordStrength } from "./use-passwords";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// test("test usePasswordField", () => {
 | 
				
			||||||
 | 
					//   const { inputType, togglePasswordShow, passwordIcon } = usePasswordField();
 | 
				
			||||||
 | 
					//   expect(inputType.value).toBe("password");
 | 
				
			||||||
 | 
					//   expect(passwordIcon.value).toBe("mdi-eye");
 | 
				
			||||||
 | 
					//   togglePasswordShow();
 | 
				
			||||||
 | 
					//   expect(inputType.value).toBe("text");
 | 
				
			||||||
 | 
					//   expect(passwordIcon.value).toBe("mdi-eye-off");
 | 
				
			||||||
 | 
					// });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("test usePasswordStrength", () => {
 | 
				
			||||||
 | 
					  test("weak password", () => {
 | 
				
			||||||
 | 
					    const password = ref("123456");
 | 
				
			||||||
 | 
					    const { score, strength, color } = usePasswordStrength(password);
 | 
				
			||||||
 | 
					    expect(score.value).toBeGreaterThan(0);
 | 
				
			||||||
 | 
					    expect(score.value).toBeLessThan(40);
 | 
				
			||||||
 | 
					    expect(strength.value).toBe("Weak");
 | 
				
			||||||
 | 
					    expect(color.value).toBe("error");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test("very strong password", () => {
 | 
				
			||||||
 | 
					    const password = ref("My~Secret~Not~So~Secret?123");
 | 
				
			||||||
 | 
					    const { score, strength, color } = usePasswordStrength(password);
 | 
				
			||||||
 | 
					    expect(score.value).toBeGreaterThan(90);
 | 
				
			||||||
 | 
					    expect(score.value).toBe(100);
 | 
				
			||||||
 | 
					    expect(strength.value).toBe("Very Strong");
 | 
				
			||||||
 | 
					    expect(color.value).toBe("success");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { computed, Ref, ref, useContext } from "@nuxtjs/composition-api";
 | 
					import { computed, Ref, ref, useContext } from "@nuxtjs/composition-api";
 | 
				
			||||||
 | 
					import { scorePassword } from "~/lib/validators";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function usePasswordField() {
 | 
					export function usePasswordField() {
 | 
				
			||||||
  const show = ref(false);
 | 
					  const show = ref(false);
 | 
				
			||||||
@@ -21,46 +22,6 @@ export function usePasswordField() {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function scorePassword(pass: string): number {
 | 
					 | 
				
			||||||
  let score = 0;
 | 
					 | 
				
			||||||
  if (!pass) return score;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const flaggedWords = ["password", "mealie", "admin", "qwerty", "login"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (pass.length < 6) return score;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Check for flagged words
 | 
					 | 
				
			||||||
  for (const word of flaggedWords) {
 | 
					 | 
				
			||||||
    if (pass.toLowerCase().includes(word)) {
 | 
					 | 
				
			||||||
      score -= 100;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // award every unique letter until 5 repetitions
 | 
					 | 
				
			||||||
  const letters: { [key: string]: number } = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (let i = 0; i < pass.length; i++) {
 | 
					 | 
				
			||||||
    letters[pass[i]] = (letters[pass[i]] || 0) + 1;
 | 
					 | 
				
			||||||
    score += 5.0 / letters[pass[i]];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // bonus points for mixing it up
 | 
					 | 
				
			||||||
  const variations: { [key: string]: boolean } = {
 | 
					 | 
				
			||||||
    digits: /\d/.test(pass),
 | 
					 | 
				
			||||||
    lower: /[a-z]/.test(pass),
 | 
					 | 
				
			||||||
    upper: /[A-Z]/.test(pass),
 | 
					 | 
				
			||||||
    nonWords: /\W/.test(pass),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let variationCount = 0;
 | 
					 | 
				
			||||||
  for (const check in variations) {
 | 
					 | 
				
			||||||
    variationCount += variations[check] === true ? 1 : 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  score += (variationCount - 1) * 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return score;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const usePasswordStrength = (password: Ref<string>) => {
 | 
					export const usePasswordStrength = (password: Ref<string>) => {
 | 
				
			||||||
  const score = computed(() => {
 | 
					  const score = computed(() => {
 | 
				
			||||||
    return scorePassword(password.value);
 | 
					    return scorePassword(password.value);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
					import { useAsync, ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { useUserApi } from "~/composables/api";
 | 
					import { useUserApi } from "~/composables/api";
 | 
				
			||||||
import { UserIn, UserOut } from "~/types/api-types/user";
 | 
					import { UserIn, UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
TODO: Potentially combine useAllUsers and useUser by delaying the get all users functionality
 | 
					TODO: Potentially combine useAllUsers and useUser by delaying the get all users functionality
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,15 @@
 | 
				
			|||||||
import { ref, Ref } from "@nuxtjs/composition-api";
 | 
					import { ref, Ref } from "@nuxtjs/composition-api";
 | 
				
			||||||
import { RequestResponse } from "~/types/api";
 | 
					import { RequestResponse } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { ValidationResponse } from "~/types/api-types/response";
 | 
					import { ValidationResponse } from "~/lib/api/types/response";
 | 
				
			||||||
 | 
					import { required, email, whitespace, url, minLength, maxLength } from "~/lib/validators";
 | 
				
			||||||
const EMAIL_REGEX =
 | 
					 | 
				
			||||||
  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const validators = {
 | 
					export const validators = {
 | 
				
			||||||
  required: (v: string) => !!v || "This Field is Required",
 | 
					  required,
 | 
				
			||||||
  email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
 | 
					  email,
 | 
				
			||||||
  whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed",
 | 
					  whitespace,
 | 
				
			||||||
  url: (v: string) => !v || URL_REGEX.test(v) || "Must Be A Valid URL",
 | 
					  url,
 | 
				
			||||||
  minLength: (min: number) => (v: string) => !v || v.length >= min || `Must Be At Least ${min} Characters`,
 | 
					  minLength,
 | 
				
			||||||
  maxLength: (max: number) => (v: string) => !v || v.length <= max || `Must Be At Most ${max} Characters`,
 | 
					  maxLength,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { AdminAboutInfo, DockerVolumeText, CheckAppConfig } from "~/types/api-types/admin";
 | 
					import { AdminAboutInfo, DockerVolumeText, CheckAppConfig } from "~/lib/api/types/admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { MealieAnalytics } from "~/types/api-types/analytics";
 | 
					import { MealieAnalytics } from "~/lib/api/types/analytics";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { AllBackups } from "~/types/api-types/admin";
 | 
					import { AllBackups } from "~/lib/api/types/admin";
 | 
				
			||||||
import { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/types/api-types/response";
 | 
					import { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/lib/api/types/response";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { BaseCRUDAPI } from "../_base";
 | 
					import { BaseCRUDAPI } from "../base/base-clients";
 | 
				
			||||||
import { GroupBase, GroupInDB } from "~/types/api-types/user";
 | 
					import { GroupBase, GroupInDB } from "~/lib/api/types/user";
 | 
				
			||||||
import { GroupAdminUpdate } from "~/types/api-types/group";
 | 
					import { GroupAdminUpdate } from "~/lib/api/types/group";
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes = {
 | 
					const routes = {
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { SuccessResponse } from "~/types/api-types/response";
 | 
					import { SuccessResponse } from "~/lib/api/types/response";
 | 
				
			||||||
import { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/types/api-types/admin";
 | 
					import { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/lib/api/types/admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { ServerTask } from "~/types/api-types/server";
 | 
					import { ServerTask } from "~/lib/api/types/server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseCRUDAPI } from "../_base";
 | 
					import { BaseCRUDAPI } from "../base/base-clients";
 | 
				
			||||||
import { UnlockResults, UserIn, UserOut } from "~/types/api-types/user";
 | 
					import { UnlockResults, UserIn, UserOut } from "~/lib/api/types/user";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { ApiRequestInstance, PaginationData } from "~/types/api";
 | 
					import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CrudAPIInterface {
 | 
					export interface CrudAPIInterface {
 | 
				
			||||||
  requests: ApiRequestInstance;
 | 
					  requests: ApiRequestInstance;
 | 
				
			||||||
@@ -18,7 +18,10 @@ export abstract class BaseAPI {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class BaseCRUDAPI<CreateType, ReadType, UpdateType = CreateType> extends BaseAPI implements CrudAPIInterface {
 | 
					export abstract class BaseCRUDAPI<CreateType, ReadType, UpdateType = CreateType>
 | 
				
			||||||
 | 
					  extends BaseAPI
 | 
				
			||||||
 | 
					  implements CrudAPIInterface
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
  abstract baseRoute: string;
 | 
					  abstract baseRoute: string;
 | 
				
			||||||
  abstract itemRoute(itemId: string | number): string;
 | 
					  abstract itemRoute(itemId: string | number): string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								frontend/lib/api/base/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/lib/api/base/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export { route } from "./route";
 | 
				
			||||||
							
								
								
									
										38
									
								
								frontend/lib/api/base/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/lib/api/base/route.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					const parts = {
 | 
				
			||||||
 | 
					  host: "http://localhost.com",
 | 
				
			||||||
 | 
					  prefix: "/api",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function overrideParts(host: string, prefix: string) {
 | 
				
			||||||
 | 
					  parts.host = host;
 | 
				
			||||||
 | 
					  parts.prefix = prefix;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type QueryValue = string | string[] | number | number[] | boolean | null | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * route is a the main URL builder for the API. It will use a predefined host and prefix (global)
 | 
				
			||||||
 | 
					 * in the urls.ts file and then append the passed in path parameter uring the `URL` class from the
 | 
				
			||||||
 | 
					 * browser. It will also append any query parameters passed in as the second parameter.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The default host `http://localhost.com` is removed from the path if it is present. This allows us
 | 
				
			||||||
 | 
					 * to bootstrap the API with different hosts as needed (like for testing) but still allows us to use
 | 
				
			||||||
 | 
					 * relative URLs in production because the API and client bundle are served from the same server/host.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function route(rest: string, params: Record<string, QueryValue> | null = null): string {
 | 
				
			||||||
 | 
					  const url = new URL(parts.prefix + rest, parts.host);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (params) {
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(params)) {
 | 
				
			||||||
 | 
					      if (Array.isArray(value)) {
 | 
				
			||||||
 | 
					        for (const item of value) {
 | 
				
			||||||
 | 
					          url.searchParams.append(key, String(item));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        url.searchParams.append(key, String(value));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return url.toString().replace("http://localhost.com", "");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								frontend/lib/api/base/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/lib/api/base/routes.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { describe, expect, it } from "vitest";
 | 
				
			||||||
 | 
					import { route } from ".";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("UrlBuilder", () => {
 | 
				
			||||||
 | 
					  it("basic query parameter", () => {
 | 
				
			||||||
 | 
					    const result = route("/test", { a: "b" });
 | 
				
			||||||
 | 
					    expect(result).toBe("/api/test?a=b");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("multiple query parameters", () => {
 | 
				
			||||||
 | 
					    const result = route("/test", { a: "b", c: "d" });
 | 
				
			||||||
 | 
					    expect(result).toBe("/api/test?a=b&c=d");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("no query parameters", () => {
 | 
				
			||||||
 | 
					    const result = route("/test");
 | 
				
			||||||
 | 
					    expect(result).toBe("/api/test");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("list-like query parameters", () => {
 | 
				
			||||||
 | 
					    const result = route("/test", { a: ["b", "c"] });
 | 
				
			||||||
 | 
					    expect(result).toBe("/api/test?a=b&a=c");
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -5,7 +5,7 @@ import { AdminGroupsApi } from "./admin/admin-groups";
 | 
				
			|||||||
import { AdminBackupsApi } from "./admin/admin-backups";
 | 
					import { AdminBackupsApi } from "./admin/admin-backups";
 | 
				
			||||||
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
 | 
					import { AdminMaintenanceApi } from "./admin/admin-maintenance";
 | 
				
			||||||
import { AdminAnalyticsApi } from "./admin/admin-analytics";
 | 
					import { AdminAnalyticsApi } from "./admin/admin-analytics";
 | 
				
			||||||
import { ApiRequestInstance } from "~/types/api";
 | 
					import { ApiRequestInstance } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AdminAPI {
 | 
					export class AdminAPI {
 | 
				
			||||||
  public about: AdminAboutAPI;
 | 
					  public about: AdminAboutAPI;
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { ValidatorsApi } from "./public/validators";
 | 
					import { ValidatorsApi } from "./public/validators";
 | 
				
			||||||
import { ExploreApi } from "./public/explore";
 | 
					import { ExploreApi } from "./public/explore";
 | 
				
			||||||
import { SharedApi } from "./public/shared";
 | 
					import { SharedApi } from "./public/shared";
 | 
				
			||||||
import { ApiRequestInstance } from "~/types/api";
 | 
					import { ApiRequestInstance } from "~/lib/api/types/non-generated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PublicApi {
 | 
					export class PublicApi {
 | 
				
			||||||
  public validators: ValidatorsApi;
 | 
					  public validators: ValidatorsApi;
 | 
				
			||||||
@@ -1,33 +1,32 @@
 | 
				
			|||||||
import { RecipeAPI } from "./class-interfaces/recipes";
 | 
					import { RecipeAPI } from "./user/recipes";
 | 
				
			||||||
import { UserApi } from "./class-interfaces/users";
 | 
					import { UserApi } from "./user/users";
 | 
				
			||||||
import { GroupAPI } from "./class-interfaces/groups";
 | 
					import { GroupAPI } from "./user/groups";
 | 
				
			||||||
import { BackupAPI } from "./class-interfaces/backups";
 | 
					import { BackupAPI } from "./user/backups";
 | 
				
			||||||
import { UploadFile } from "./class-interfaces/upload";
 | 
					import { UploadFile } from "./user/upload";
 | 
				
			||||||
import { CategoriesAPI } from "./class-interfaces/organizer-categories";
 | 
					import { CategoriesAPI } from "./user/organizer-categories";
 | 
				
			||||||
import { TagsAPI } from "./class-interfaces/organizer-tags";
 | 
					import { TagsAPI } from "./user/organizer-tags";
 | 
				
			||||||
import { UtilsAPI } from "./class-interfaces/utils";
 | 
					import { UtilsAPI } from "./user/utils";
 | 
				
			||||||
import { FoodAPI } from "./class-interfaces/recipe-foods";
 | 
					import { FoodAPI } from "./user/recipe-foods";
 | 
				
			||||||
import { UnitAPI } from "./class-interfaces/recipe-units";
 | 
					import { UnitAPI } from "./user/recipe-units";
 | 
				
			||||||
import { CookbookAPI } from "./class-interfaces/group-cookbooks";
 | 
					import { CookbookAPI } from "./user/group-cookbooks";
 | 
				
			||||||
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
 | 
					import { WebhooksAPI } from "./user/group-webhooks";
 | 
				
			||||||
import { RegisterAPI } from "./class-interfaces/user-registration";
 | 
					import { RegisterAPI } from "./user/user-registration";
 | 
				
			||||||
import { MealPlanAPI } from "./class-interfaces/group-mealplan";
 | 
					import { MealPlanAPI } from "./user/group-mealplan";
 | 
				
			||||||
import { EmailAPI } from "./class-interfaces/email";
 | 
					import { EmailAPI } from "./user/email";
 | 
				
			||||||
import { BulkActionsAPI } from "./class-interfaces/recipe-bulk-actions";
 | 
					import { BulkActionsAPI } from "./user/recipe-bulk-actions";
 | 
				
			||||||
import { GroupServerTaskAPI } from "./class-interfaces/group-tasks";
 | 
					import { GroupServerTaskAPI } from "./user/group-tasks";
 | 
				
			||||||
import { AdminAPI } from "./admin-api";
 | 
					import { ToolsApi } from "./user/organizer-tools";
 | 
				
			||||||
import { ToolsApi } from "./class-interfaces/organizer-tools";
 | 
					import { GroupMigrationApi } from "./user/group-migrations";
 | 
				
			||||||
import { GroupMigrationApi } from "./class-interfaces/group-migrations";
 | 
					import { GroupReportsApi } from "./user/group-reports";
 | 
				
			||||||
import { GroupReportsApi } from "./class-interfaces/group-reports";
 | 
					import { ShoppingApi } from "./user/group-shopping-lists";
 | 
				
			||||||
import { ShoppingApi } from "./class-interfaces/group-shopping-lists";
 | 
					import { MultiPurposeLabelsApi } from "./user/group-multiple-purpose-labels";
 | 
				
			||||||
import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose-labels";
 | 
					import { GroupEventNotifierApi } from "./user/group-event-notifier";
 | 
				
			||||||
import { GroupEventNotifierApi } from "./class-interfaces/group-event-notifier";
 | 
					import { MealPlanRulesApi } from "./user/group-mealplan-rules";
 | 
				
			||||||
import { MealPlanRulesApi } from "./class-interfaces/group-mealplan-rules";
 | 
					import { GroupDataSeederApi } from "./user/group-seeder";
 | 
				
			||||||
import { GroupDataSeederApi } from "./class-interfaces/group-seeder";
 | 
					import { OcrAPI } from "./user/ocr";
 | 
				
			||||||
import {OcrAPI} from "./class-interfaces/ocr";
 | 
					import { ApiRequestInstance } from "~/lib/api/types/non-generated";
 | 
				
			||||||
import { ApiRequestInstance } from "~/types/api";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Api {
 | 
					export class UserApiClient {
 | 
				
			||||||
  public recipes: RecipeAPI;
 | 
					  public recipes: RecipeAPI;
 | 
				
			||||||
  public users: UserApi;
 | 
					  public users: UserApi;
 | 
				
			||||||
  public groups: GroupAPI;
 | 
					  public groups: GroupAPI;
 | 
				
			||||||
@@ -98,5 +97,3 @@ class Api {
 | 
				
			|||||||
    Object.freeze(this);
 | 
					    Object.freeze(this);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { Api, AdminAPI };
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								frontend/lib/api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/lib/api/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export { AdminAPI } from "./client-admin";
 | 
				
			||||||
 | 
					export { PublicApi } from "./client-public";
 | 
				
			||||||
 | 
					export { UserApiClient as UserApi } from "./client-user";
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { Recipe } from "~/types/api-types/recipe";
 | 
					import { Recipe } from "~/lib/api/types/recipe";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { BaseAPI } from "../_base";
 | 
					import { BaseAPI } from "../base/base-clients";
 | 
				
			||||||
import { ValidationResponse } from "~/types/api-types/response";
 | 
					import { ValidationResponse } from "~/lib/api/types/response";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const prefix = "/api";
 | 
					const prefix = "/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user