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:
Hayden 2022-10-22 11:51:07 -08:00 committed by GitHub
parent 9f6bcc83d5
commit fcc5d99d40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
182 changed files with 902 additions and 487 deletions

View file

@ -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
View file

@ -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/

View file

@ -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"]

View file

@ -1 +0,0 @@
_

View file

@ -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: {

View file

@ -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: {

View file

@ -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({

View file

@ -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";

View file

@ -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: {

View file

@ -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);

View file

@ -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";

View file

@ -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({

View file

@ -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";

View file

@ -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";

View file

@ -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: {

View file

@ -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";

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {},

View file

@ -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";

View file

@ -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: {

View file

@ -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: {

View file

@ -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: {

View file

@ -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

View file

@ -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

View file

@ -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";

View file

@ -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;

View file

@ -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: {

View file

@ -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";

View file

@ -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({

View file

@ -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";

View file

@ -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: {

View file

@ -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: {

View file

@ -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";

View file

@ -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({

View file

@ -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 {

View file

@ -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";

View file

@ -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: {

View file

@ -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";

View file

@ -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 = {

View file

@ -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: {

View file

@ -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({

View file

@ -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: {

View file

@ -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 {

View file

@ -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: {

View file

@ -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}`;
}); });

View file

@ -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: {

View file

@ -27,4 +27,3 @@ export default defineComponent({
}, },
}); });
</script> </script>

View file

@ -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: {

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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",

View file

@ -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) {

View file

@ -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 };
}; };

View file

@ -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({

View file

@ -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>({

View file

@ -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();

View file

@ -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,
}; };
}; };

View file

@ -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([]);

View file

@ -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;

View file

@ -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;

View file

@ -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([]);

View file

@ -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([]);

View file

@ -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;

View file

@ -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 {

View file

@ -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 = "",

View file

@ -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;

View file

@ -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" },

View file

@ -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();

View file

@ -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();

View 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");
});
});

View file

@ -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);

View file

@ -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

View file

@ -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,
}; };
/** /**

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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 = {

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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;

View file

@ -0,0 +1 @@
export { route } from "./route";

View 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", "");
}

View 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");
});
});

View file

@ -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;

View file

@ -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;

View file

@ -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 };

View file

@ -0,0 +1,3 @@
export { AdminAPI } from "./client-admin";
export { PublicApi } from "./client-public";
export { UserApiClient as UserApi } from "./client-user";

View file

@ -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";

View file

@ -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";

View file

@ -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