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:
parent
9f6bcc83d5
commit
fcc5d99d40
182 changed files with 902 additions and 487 deletions
4
.github/workflows/partial-frontend.yml
vendored
4
.github/workflows/partial-frontend.yml
vendored
|
@ -38,6 +38,10 @@ jobs:
|
|||
run: yarn lint
|
||||
working-directory: "frontend"
|
||||
|
||||
- name: Run tests 🧪
|
||||
run: yarn test:ci
|
||||
working-directory: "frontend"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -55,7 +55,6 @@ develop-eggs/
|
|||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
!frontend/src/components/Recipe/Parts/
|
||||
|
|
|
@ -75,7 +75,7 @@ def generate_typescript_types() -> None:
|
|||
return str_path
|
||||
|
||||
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"]
|
||||
|
||||
|
|
1
frontend/.husky/.gitignore
vendored
1
frontend/.husky/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
_
|
|
@ -20,7 +20,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { parseISO, formatDistanceToNow } from "date-fns";
|
||||
import { GroupDataExport } from "~/types/api-types/group";
|
||||
import { GroupDataExport } from "~/lib/api/types/group";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
exports: {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
|
||||
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({
|
||||
components: {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import RecipeContextMenu from "./RecipeContextMenu.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 DELETE_EVENT = "delete";
|
||||
|
|
|
@ -82,7 +82,7 @@ import { defineComponent, reactive, useContext } from "@nuxtjs/composition-api";
|
|||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { detectServerBaseUrl } from "~/composables/use-utils";
|
||||
import { RecipeAsset } from "~/types/api-types/recipe";
|
||||
import { RecipeAsset } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -129,7 +129,7 @@ import RecipeCard from "./RecipeCard.vue";
|
|||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
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";
|
||||
|
||||
const REPLACE_RECIPES_EVENT = "replaceRecipes";
|
||||
|
@ -236,7 +236,7 @@ export default defineComponent({
|
|||
cookbook.value,
|
||||
category.value,
|
||||
tag.value,
|
||||
tool.value,
|
||||
tool.value
|
||||
);
|
||||
|
||||
// since we doubled the first call, we also need to advance the page
|
||||
|
@ -263,7 +263,7 @@ export default defineComponent({
|
|||
cookbook.value,
|
||||
category.value,
|
||||
tag.value,
|
||||
tool.value,
|
||||
tool.value
|
||||
);
|
||||
if (!newRecipes.length) {
|
||||
hasMore.value = false;
|
||||
|
@ -325,7 +325,7 @@ export default defineComponent({
|
|||
cookbook.value,
|
||||
category.value,
|
||||
tag.value,
|
||||
tool.value,
|
||||
tool.value
|
||||
);
|
||||
context.emit(REPLACE_RECIPES_EVENT, newRecipes);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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";
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-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";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
@ -101,8 +101,8 @@ import RecipeDialogShare from "./RecipeDialogShare.vue";
|
|||
import { useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { planTypeOptions } from "~/composables/use-group-mealplan";
|
||||
import { ShoppingListSummary } from "~/types/api-types/group";
|
||||
import { PlanEntryType } from "~/types/api-types/meal-plan";
|
||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
||||
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
||||
import { useCopy } from "~/composables/use-copy";
|
||||
|
||||
|
|
|
@ -44,9 +44,9 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
||||
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 { UserOut } from "~/types/api-types/user";
|
||||
import { UserOut } from "~/lib/api/types/user";
|
||||
|
||||
const INPUT_EVENT = "input";
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
import { defineComponent, toRefs, reactive, ref, watch, useRoute } from "@nuxtjs/composition-api";
|
||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||
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";
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, reactive, useContext } from "@nuxtjs/composition-api";
|
||||
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 { alert } from "~/composables/use-toast";
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { UserOut } from "~/types/api-types/user";
|
||||
import { UserOut } from "~/lib/api/types/user";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
slug: {
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||
import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
import { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
import { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
|
|
|
@ -230,7 +230,7 @@ import {
|
|||
useContext,
|
||||
computed,
|
||||
} 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 { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
|
||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { RecipeSummary } from "~/types/api-types/recipe";
|
||||
import { RecipeSummary } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { RecipeNote } from "~/types/api-types/recipe";
|
||||
import { RecipeNote } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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({
|
||||
props: {
|
||||
|
|
|
@ -146,9 +146,9 @@ import { until } from "@vueuse/core";
|
|||
import { invoke } from "@vueuse/shared";
|
||||
import draggable from "vuedraggable";
|
||||
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 { 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 BannerExperimental from "~/components/global/BannerExperimental.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 RecipeOcrEditorPageHelp from "~/components/Domain/Recipe/RecipeOcrEditorPage/RecipeOcrEditorPageParts/RecipeOcrEditorPageHelp.vue";
|
||||
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
|
||||
// https://github.com/phillipdupuis/pydantic-to-typescript/issues/28
|
||||
|
|
|
@ -41,8 +41,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, reactive, useContext, ref, toRefs, watch } from "@nuxtjs/composition-api";
|
||||
import { onMounted } from "vue-demi";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { OcrTsvResponse as NullableOcrTsvResponse } from "~/types/api-types/ocr";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { OcrTsvResponse as NullableOcrTsvResponse } from "~/lib/api/types/ocr";
|
||||
import { CanvasModes, SelectedTextSplitModes, ImagePosition, Mouse, CanvasRect, ToolbarIcons } from "~/types/ocr-types";
|
||||
|
||||
// Temporary Shim until we have a better solution
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
import { computed, defineComponent, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
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";
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
|
||||
import { useContextPresets } from "~/composables/use-context-presents";
|
||||
import RecipeOrganizerDialog from "~/components/Domain/Recipe/RecipeOrganizerDialog.vue";
|
||||
import { RecipeOrganizer } from "~/types/recipe/organizers";
|
||||
import { RecipeOrganizer } from "~/lib/api/types/non-generated";
|
||||
|
||||
interface GenericItem {
|
||||
id?: string;
|
||||
|
|
|
@ -42,11 +42,11 @@
|
|||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { computed, onMounted } from "vue-demi";
|
||||
import RecipeOrganizerDialog from "./RecipeOrganizerDialog.vue";
|
||||
import { RecipeCategory, RecipeTag } from "~/types/api-types/user";
|
||||
import { RecipeTool } from "~/types/api-types/admin";
|
||||
import { RecipeCategory, RecipeTag } from "~/lib/api/types/user";
|
||||
import { RecipeTool } from "~/lib/api/types/admin";
|
||||
import { useTagStore } from "~/composables/store/use-tag-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({
|
||||
components: {
|
||||
|
|
|
@ -90,8 +90,8 @@ import RecipePageTitleContent from "./RecipePageParts/RecipePageTitleContent.vue
|
|||
import RecipePageComments from "./RecipePageParts/RecipePageComments.vue";
|
||||
import RecipePrintView from "~/components/Domain/Recipe/RecipePrintView.vue";
|
||||
import { EditorMode, PageMode, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { useRecipeMeta } from "~/composables/recipes";
|
||||
import { useRouteQuery } from "~/composables/use-router";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
|
|
@ -56,9 +56,9 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, ref, toRefs, onMounted, reactive } from "@nuxtjs/composition-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 { NoUndefinedField } from "~/types/api";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, onUnmounted } from "@nuxtjs/composition-api";
|
||||
import { clearPageState, usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
|
||||
import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
|
||||
|
|
|
@ -57,8 +57,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
recipe: {
|
||||
|
|
|
@ -61,8 +61,8 @@ import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
|||
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
|
||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||
import { useStaticRoutes } from "~/composables/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { usePageState, usePageUser, PageMode, EditorMode } from "~/composables/recipe-page/shared-state";
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
|
|
@ -55,8 +55,8 @@
|
|||
import draggable from "vuedraggable";
|
||||
import { computed, defineComponent, ref } from "@nuxtjs/composition-api";
|
||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
|
||||
import RecipeDialogBulkAdd from "~/components/Domain/Recipe/RecipeDialogBulkAdd.vue";
|
||||
import { uuid4 } from "~/composables/use-utils";
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
@ -225,12 +225,12 @@ import {
|
|||
computed,
|
||||
} from "@nuxtjs/composition-api";
|
||||
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 { uuid4, detectServerBaseUrl } from "~/composables/use-utils";
|
||||
import { useUserApi, useStaticRoutes } from "~/composables/api";
|
||||
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";
|
||||
|
||||
interface MergerHistory {
|
||||
|
|
|
@ -59,8 +59,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import RecipeOrganizerSelector from "@/components/Domain/Recipe/RecipeOrganizerSelector.vue";
|
||||
import RecipeNutrition from "~/components/Domain/Recipe/RecipeNutrition.vue";
|
||||
import RecipeChips from "@/components/Domain/Recipe/RecipeChips.vue";
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
import RecipeScaleEditButton from "~/components/Domain/Recipe/RecipeScaleEditButton.vue";
|
||||
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { usePageState } from "~/composables/recipe-page/shared-state";
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { NoUndefinedField } from "~/types/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||
|
||||
|
|
|
@ -25,7 +25,10 @@
|
|||
<h4 v-if="ingredientSection.ingredients[0].title" class="ingredient-title mt-2">
|
||||
{{ ingredientSection.ingredients[0].title }}
|
||||
</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">
|
||||
<p :key="`ingredient-${ingredientIndex}`" class="ingredient-body" v-html="parseText(ingredient)" />
|
||||
</template>
|
||||
|
@ -70,7 +73,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
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";
|
||||
|
||||
type IngredientSection = {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { RecipeSettings } from "~/types/api-types/recipe";
|
||||
import { RecipeSettings } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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";
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
import { MultiPurposeLabelSummary } from "~/types/api-types/recipe";
|
||||
import { MultiPurposeLabelSummary } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -55,9 +55,9 @@
|
|||
import { defineComponent, computed, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import ShoppingListItemEditor from "./ShoppingListItemEditor.vue";
|
||||
import MultiPurposeLabel from "./MultiPurposeLabel.vue";
|
||||
import { ShoppingListItemCreate } from "~/types/api-types/group";
|
||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
|
||||
import { ShoppingListItemCreate } from "~/lib/api/types/group";
|
||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||
import { getDisplayText } from "~/composables/use-display-text";
|
||||
|
||||
interface actions {
|
||||
|
|
|
@ -96,9 +96,9 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/types/api-types/group";
|
||||
import { MultiPurposeLabelOut } from "~/types/api-types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
|
||||
import { ShoppingListItemCreate, ShoppingListItemOut } from "~/lib/api/types/group";
|
||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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({
|
||||
props: {
|
||||
|
@ -35,7 +35,7 @@ export default defineComponent({
|
|||
|
||||
const imageURL = computed(() => {
|
||||
// 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 ?? "";
|
||||
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
||||
});
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
* using the .sync syntax `item-id.sync="item.labelId"`
|
||||
*/
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
|
||||
import { MultiPurposeLabelSummary } from "~/lib/api/types/labels";
|
||||
import { IngredientFood, IngredientUnit } from "~/lib/api/types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
|
@ -27,4 +27,3 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
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({
|
||||
props: {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { useContext } from "@nuxtjs/composition-api";
|
||||
import type { NuxtAxiosInstance } from "@nuxtjs/axios";
|
||||
import { AdminAPI, Api } from "~/api";
|
||||
import { ApiRequestInstance, RequestResponse } from "~/types/api";
|
||||
import { PublicApi } from "~/api/public-api";
|
||||
import { ApiRequestInstance, RequestResponse } from "~/lib/api/types/non-generated";
|
||||
import { AdminAPI, PublicApi, UserApi } from "~/lib/api";
|
||||
|
||||
const request = {
|
||||
async safe<T, U>(
|
||||
|
@ -66,12 +65,12 @@ export const useAdminApi = function (): AdminAPI {
|
|||
return new AdminAPI(requests);
|
||||
};
|
||||
|
||||
export const useUserApi = function (): Api {
|
||||
export const useUserApi = function (): UserApi {
|
||||
const { $axios, i18n } = useContext();
|
||||
$axios.setHeader("Accept-Language", i18n.locale);
|
||||
|
||||
const requests = getRequests($axios);
|
||||
return new Api(requests);
|
||||
return new UserApi(requests);
|
||||
};
|
||||
|
||||
export const usePublicApi = function (): PublicApi {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ref, Ref, useAsync, useContext } from "@nuxtjs/composition-api";
|
||||
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> {
|
||||
const appInfo = ref<null | AppInfo>(null);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Ref, useAsync } from "@nuxtjs/composition-api";
|
||||
import { useAsyncKey } from "../use-utils";
|
||||
import { BaseCRUDAPI } from "~/api/_base";
|
||||
import { BaseCRUDAPI } from "~/lib/api/base/base-clients";
|
||||
|
||||
type BoundT = {
|
||||
id?: string | number;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
EDIT = "EDIT",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import DOMPurify from "isomorphic-dompurify";
|
||||
import { useFraction } from "./use-fraction";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
import { RecipeIngredient } from "~/lib/api/types/recipe";
|
||||
const { frac } = useFraction();
|
||||
|
||||
function sanitizeIngredientHTML(rawHtml: string) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Ref } from "@nuxtjs/composition-api";
|
||||
import { useStaticRoutes } from "~/composables/api";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
|
||||
export interface RecipeMeta {
|
||||
title?: string;
|
||||
|
@ -53,6 +53,6 @@ export const useRecipeMeta = () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
return { recipeMeta };
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { computed, reactive, ref, Ref } from "@nuxtjs/composition-api";
|
||||
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>) => {
|
||||
const localState = reactive({
|
||||
|
|
|
@ -2,7 +2,7 @@ import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
|
|||
import { useAsyncKey } from "../use-utils";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
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) {
|
||||
const workingToolData = reactive<RecipeTool>({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ref, onMounted } from "@nuxtjs/composition-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) {
|
||||
const api = useUserApi();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAsync, ref } from "@nuxtjs/composition-api";
|
||||
import { useAsyncKey } from "../use-utils";
|
||||
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 recentRecipes = ref<Recipe[]>([]);
|
||||
|
@ -21,7 +21,14 @@ export const useLazyRecipes = function () {
|
|||
tag: 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 : [];
|
||||
}
|
||||
|
||||
|
@ -54,7 +61,7 @@ export const useLazyRecipes = function () {
|
|||
appendRecipes,
|
||||
assignSorted,
|
||||
removeRecipe,
|
||||
replaceRecipes
|
||||
replaceRecipes,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "../partials/use-actions-factory";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { RecipeCategory } from "~/types/api-types/admin";
|
||||
import { RecipeCategory } from "~/lib/api/types/admin";
|
||||
|
||||
const categoryStore: Ref<RecipeCategory[]> = ref([]);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ref, reactive, Ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "../partials/use-actions-factory";
|
||||
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;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
|
||||
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";
|
||||
|
||||
let labelStore: Ref<MultiPurposeLabelOut[] | null> | null = null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "../partials/use-actions-factory";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { RecipeTag } from "~/types/api-types/admin";
|
||||
import { RecipeTag } from "~/lib/api/types/admin";
|
||||
|
||||
const items: Ref<RecipeTag[]> = ref([]);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reactive, ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "../partials/use-actions-factory";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { RecipeTool } from "~/types/api-types/recipe";
|
||||
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||
|
||||
const toolStore: Ref<RecipeTool[]> = ref([]);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ref, reactive, Ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "../partials/use-actions-factory";
|
||||
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;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
|
||||
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";
|
||||
|
||||
interface ImportBackup {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* 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(
|
||||
notes = "",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAsync, ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { useAsyncKey } from "./use-utils";
|
||||
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;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useAsync, ref, Ref, watch } from "@nuxtjs/composition-api";
|
|||
import { format } from "date-fns";
|
||||
import { useAsyncKey } from "./use-utils";
|
||||
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 = [
|
||||
{ text: "Breakfast", value: "breakfast" },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAsync, ref } from "@nuxtjs/composition-api";
|
||||
import { useAsyncKey } from "./use-utils";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { ReadWebhook } from "~/types/api-types/group";
|
||||
import { ReadWebhook } from "~/lib/api/types/group";
|
||||
|
||||
export const useGroupWebhooks = function () {
|
||||
const api = useUserApi();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useAsync, ref } from "@nuxtjs/composition-api";
|
||||
import { useAsyncKey } from "./use-utils";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { GroupBase } from "~/types/api-types/user";
|
||||
import { GroupBase } from "~/lib/api/types/user";
|
||||
|
||||
export const useGroupSelf = function () {
|
||||
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 { scorePassword } from "~/lib/validators";
|
||||
|
||||
export function usePasswordField() {
|
||||
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>) => {
|
||||
const score = computed(() => {
|
||||
return scorePassword(password.value);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useAsync, ref } from "@nuxtjs/composition-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
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import { ref, Ref } from "@nuxtjs/composition-api";
|
||||
import { RequestResponse } from "~/types/api";
|
||||
import { ValidationResponse } from "~/types/api-types/response";
|
||||
|
||||
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()@:%_+.~#?&//=]*)/;
|
||||
import { RequestResponse } from "~/lib/api/types/non-generated";
|
||||
import { ValidationResponse } from "~/lib/api/types/response";
|
||||
import { required, email, whitespace, url, minLength, maxLength } from "~/lib/validators";
|
||||
|
||||
export const validators = {
|
||||
required: (v: string) => !!v || "This Field is Required",
|
||||
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
|
||||
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed",
|
||||
url: (v: string) => !v || URL_REGEX.test(v) || "Must Be A Valid URL",
|
||||
minLength: (min: number) => (v: string) => !v || v.length >= min || `Must Be At Least ${min} Characters`,
|
||||
maxLength: (max: number) => (v: string) => !v || v.length <= max || `Must Be At Most ${max} Characters`,
|
||||
required,
|
||||
email,
|
||||
whitespace,
|
||||
url,
|
||||
minLength,
|
||||
maxLength,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { AdminAboutInfo, DockerVolumeText, CheckAppConfig } from "~/types/api-types/admin";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { AdminAboutInfo, DockerVolumeText, CheckAppConfig } from "~/lib/api/types/admin";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { MealieAnalytics } from "~/types/api-types/analytics";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { MealieAnalytics } from "~/lib/api/types/analytics";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { AllBackups } from "~/types/api-types/admin";
|
||||
import { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/types/api-types/response";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { AllBackups } from "~/lib/api/types/admin";
|
||||
import { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/lib/api/types/response";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseCRUDAPI } from "../_base";
|
||||
import { GroupBase, GroupInDB } from "~/types/api-types/user";
|
||||
import { GroupAdminUpdate } from "~/types/api-types/group";
|
||||
import { BaseCRUDAPI } from "../base/base-clients";
|
||||
import { GroupBase, GroupInDB } from "~/lib/api/types/user";
|
||||
import { GroupAdminUpdate } from "~/lib/api/types/group";
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { SuccessResponse } from "~/types/api-types/response";
|
||||
import { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/types/api-types/admin";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { SuccessResponse } from "~/lib/api/types/response";
|
||||
import { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/lib/api/types/admin";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { ServerTask } from "~/types/api-types/server";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { ServerTask } from "~/lib/api/types/server";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseCRUDAPI } from "../_base";
|
||||
import { UnlockResults, UserIn, UserOut } from "~/types/api-types/user";
|
||||
import { BaseCRUDAPI } from "../base/base-clients";
|
||||
import { UnlockResults, UserIn, UserOut } from "~/lib/api/types/user";
|
||||
|
||||
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 {
|
||||
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 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 { AdminMaintenanceApi } from "./admin/admin-maintenance";
|
||||
import { AdminAnalyticsApi } from "./admin/admin-analytics";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
||||
|
||||
export class AdminAPI {
|
||||
public about: AdminAboutAPI;
|
|
@ -1,7 +1,7 @@
|
|||
import { ValidatorsApi } from "./public/validators";
|
||||
import { ExploreApi } from "./public/explore";
|
||||
import { SharedApi } from "./public/shared";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
||||
|
||||
export class PublicApi {
|
||||
public validators: ValidatorsApi;
|
|
@ -1,33 +1,32 @@
|
|||
import { RecipeAPI } from "./class-interfaces/recipes";
|
||||
import { UserApi } from "./class-interfaces/users";
|
||||
import { GroupAPI } from "./class-interfaces/groups";
|
||||
import { BackupAPI } from "./class-interfaces/backups";
|
||||
import { UploadFile } from "./class-interfaces/upload";
|
||||
import { CategoriesAPI } from "./class-interfaces/organizer-categories";
|
||||
import { TagsAPI } from "./class-interfaces/organizer-tags";
|
||||
import { UtilsAPI } from "./class-interfaces/utils";
|
||||
import { FoodAPI } from "./class-interfaces/recipe-foods";
|
||||
import { UnitAPI } from "./class-interfaces/recipe-units";
|
||||
import { CookbookAPI } from "./class-interfaces/group-cookbooks";
|
||||
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
|
||||
import { RegisterAPI } from "./class-interfaces/user-registration";
|
||||
import { MealPlanAPI } from "./class-interfaces/group-mealplan";
|
||||
import { EmailAPI } from "./class-interfaces/email";
|
||||
import { BulkActionsAPI } from "./class-interfaces/recipe-bulk-actions";
|
||||
import { GroupServerTaskAPI } from "./class-interfaces/group-tasks";
|
||||
import { AdminAPI } from "./admin-api";
|
||||
import { ToolsApi } from "./class-interfaces/organizer-tools";
|
||||
import { GroupMigrationApi } from "./class-interfaces/group-migrations";
|
||||
import { GroupReportsApi } from "./class-interfaces/group-reports";
|
||||
import { ShoppingApi } from "./class-interfaces/group-shopping-lists";
|
||||
import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose-labels";
|
||||
import { GroupEventNotifierApi } from "./class-interfaces/group-event-notifier";
|
||||
import { MealPlanRulesApi } from "./class-interfaces/group-mealplan-rules";
|
||||
import { GroupDataSeederApi } from "./class-interfaces/group-seeder";
|
||||
import {OcrAPI} from "./class-interfaces/ocr";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
import { RecipeAPI } from "./user/recipes";
|
||||
import { UserApi } from "./user/users";
|
||||
import { GroupAPI } from "./user/groups";
|
||||
import { BackupAPI } from "./user/backups";
|
||||
import { UploadFile } from "./user/upload";
|
||||
import { CategoriesAPI } from "./user/organizer-categories";
|
||||
import { TagsAPI } from "./user/organizer-tags";
|
||||
import { UtilsAPI } from "./user/utils";
|
||||
import { FoodAPI } from "./user/recipe-foods";
|
||||
import { UnitAPI } from "./user/recipe-units";
|
||||
import { CookbookAPI } from "./user/group-cookbooks";
|
||||
import { WebhooksAPI } from "./user/group-webhooks";
|
||||
import { RegisterAPI } from "./user/user-registration";
|
||||
import { MealPlanAPI } from "./user/group-mealplan";
|
||||
import { EmailAPI } from "./user/email";
|
||||
import { BulkActionsAPI } from "./user/recipe-bulk-actions";
|
||||
import { GroupServerTaskAPI } from "./user/group-tasks";
|
||||
import { ToolsApi } from "./user/organizer-tools";
|
||||
import { GroupMigrationApi } from "./user/group-migrations";
|
||||
import { GroupReportsApi } from "./user/group-reports";
|
||||
import { ShoppingApi } from "./user/group-shopping-lists";
|
||||
import { MultiPurposeLabelsApi } from "./user/group-multiple-purpose-labels";
|
||||
import { GroupEventNotifierApi } from "./user/group-event-notifier";
|
||||
import { MealPlanRulesApi } from "./user/group-mealplan-rules";
|
||||
import { GroupDataSeederApi } from "./user/group-seeder";
|
||||
import { OcrAPI } from "./user/ocr";
|
||||
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
|
||||
|
||||
class Api {
|
||||
export class UserApiClient {
|
||||
public recipes: RecipeAPI;
|
||||
public users: UserApi;
|
||||
public groups: GroupAPI;
|
||||
|
@ -98,5 +97,3 @@ class Api {
|
|||
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 { Recipe } from "~/types/api-types/recipe";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { Recipe } from "~/types/api-types/recipe";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
|
||||
const prefix = "/api";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { BaseAPI } from "../_base";
|
||||
import { ValidationResponse } from "~/types/api-types/response";
|
||||
import { BaseAPI } from "../base/base-clients";
|
||||
import { ValidationResponse } from "~/lib/api/types/response";
|
||||
|
||||
const prefix = "/api";
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue