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

feat(backend): Minor linting, bulk URL import, and improve BG tasks (#760)

* Fixes #751

* Fixes not showing original URL

* start slice at 0 instead of 1

* remove print statements

* add linter for print statements and remove print

* hide all buttons when edit disabled

* add bulk import API

* update attribute bindings

* unify button styles

* bulk add recipe feature

* thanks linter!

* uncomment code

Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-10-28 19:28:33 -08:00 committed by GitHub
parent 1e5ef28f91
commit 2afaf70a03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 295 additions and 65 deletions

View file

@ -1,4 +1,6 @@
import { BaseCRUDAPI } from "../_base";
import { Category } from "./categories";
import { Tag } from "./tags";
import { Recipe, CreateRecipe } from "~/types/api-types/recipe";
const prefix = "/api";
@ -8,6 +10,7 @@ const routes = {
recipesBase: `${prefix}/recipes`,
recipesTestScrapeUrl: `${prefix}/recipes/test-scrape-url`,
recipesCreateUrl: `${prefix}/recipes/create-url`,
recipesCreateUrlBulk: `${prefix}/recipes/create-url/bulk`,
recipesCreateFromZip: `${prefix}/recipes/create-from-zip`,
recipesCategory: `${prefix}/recipes/category`,
recipesParseIngredient: `${prefix}/parser/ingredient`,
@ -59,6 +62,16 @@ export interface ParsedIngredient {
ingredient: Ingredient;
}
export interface BulkCreateRecipe {
url: string;
categories: Category[];
tags: Tag[];
}
export interface BulkCreatePayload {
imports: BulkCreateRecipe[];
}
export class RecipeAPI extends BaseCRUDAPI<Recipe, CreateRecipe> {
baseRoute: string = routes.recipesBase;
itemRoute = routes.recipesRecipeSlug;
@ -90,6 +103,10 @@ export class RecipeAPI extends BaseCRUDAPI<Recipe, CreateRecipe> {
return await this.requests.post(routes.recipesCreateUrl, { url });
}
async createManyByUrl(payload: BulkCreatePayload) {
return await this.requests.post(routes.recipesCreateUrlBulk, payload);
}
// Recipe Comments
// Methods to Generate reference urls for assets/images *

View file

@ -1,3 +1,5 @@
//TODO: Prevent fetching Categories/Tags multiple time when selector is on page multiple times
<template>
<v-autocomplete
v-model="selected"
@ -14,12 +16,14 @@
:solo="solo"
:return-object="returnObject"
:flat="flat"
v-bind="$attrs"
@input="emitChange"
>
<template #selection="data">
<v-chip
v-if="showSelected"
:key="data.index"
:small="dense"
class="ma-1"
:input-value="data.selected"
close

View file

@ -26,9 +26,7 @@
</v-card>
<div v-if="edit" class="d-flex justify-end">
<v-btn class="mt-1" color="secondary" dark @click="addNote">
<v-icon>{{ $globals.icons.create }}</v-icon>
</v-btn>
<BaseButton class="ml-auto my-2" @click="addNote"> {{ $t("general.new") }}</BaseButton>
</div>
</div>
</template>

View file

@ -92,7 +92,7 @@
@end="onMoveCallback"
>
<v-card v-for="mealplan in plan.meals" :key="mealplan.id" v-model="hover[mealplan.id]" class="my-1">
<v-list-item>
<v-list-item :to="edit ? null : `/recipe/${mealplan.recipe.slug}`">
<v-list-item-avatar :rounded="false">
<RecipeCardImage v-if="mealplan.recipe" tiny icon-size="25" :slug="mealplan.recipe.slug" />
<v-icon v-else>
@ -108,8 +108,8 @@
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-divider class="mx-2"></v-divider>
<v-card-actions>
<v-divider v-if="edit" class="mx-2"></v-divider>
<v-card-actions v-if="edit">
<v-btn color="error" icon @click="actions.deleteOne(mealplan.id)">
<v-icon>{{ $globals.icons.delete }}</v-icon>
</v-btn>

View file

@ -110,8 +110,8 @@
/>
</draggable>
<div class="d-flex justify-end mt-2">
<RecipeIngredientParserMenu class="mr-1" :slug="recipe.slug" :ingredients="recipe.recipeIngredient" />
<RecipeDialogBulkAdd class="mr-1" @bulk-data="addIngredient" />
<RecipeIngredientParserMenu :slug="recipe.slug" :ingredients="recipe.recipeIngredient" />
<RecipeDialogBulkAdd class="ml-1 mr-1" @bulk-data="addIngredient" />
<BaseButton @click="addIngredient"> {{ $t("general.new") }} </BaseButton>
</div>
</div>
@ -228,6 +228,30 @@
<RecipeNotes v-model="recipe.notes" :edit="form" />
</v-col>
</v-row>
<v-card-actions class="justify-end">
<v-text-field
v-if="form"
v-model="recipe.orgURL"
class="mt-10"
:label="$t('recipe.original-url')"
></v-text-field>
<v-btn
v-else-if="recipe.orgURL"
dense
small
:hover="false"
type="label"
:ripple="false"
elevation="0"
:href="recipe.orgURL"
color="secondary darken-1"
target="_blank"
class="rounded-sm mr-4"
>
{{ $t("recipe.original-url") }}
</v-btn>
</v-card-actions>
</v-card-text>
</div>
</v-card>

View file

@ -142,7 +142,7 @@
<v-tab-item value="debug" eager>
<v-form ref="domUrlForm" @submit.prevent="debugUrl(recipeUrl)">
<v-card flat>
<v-card-title class="headline"> Recipe Debugger</v-card-title>
<v-card-title class="headline"> Recipe Importer </v-card-title>
<v-card-text>
Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe
scraper and the results will be displayed. If you don't see any data returned, the site you are trying
@ -174,6 +174,17 @@
</v-card>
</v-form>
</v-tab-item>
<v-tab-item value="bulk" eager>
<v-card flat>
<v-card-title class="headline"> Recipe Bulk Importer </v-card-title>
<v-card-text>
The Bulk recipe importer allows you to import multiple recipes at once by queing the sites on the
backend and running the task in the background. This can be useful when initially migrating to Mealie,
or when you want to import a large number of recipes.
</v-card-text>
</v-card>
</v-tab-item>
</v-tabs-items>
</section>
<v-divider class="mt-5"></v-divider>
@ -195,6 +206,74 @@
height="700px"
/>
</section>
<!-- Debug Extras -->
<section v-else-if="tab === 'bulk'" class="mt-2">
<v-row v-for="(bulkUrl, idx) in bulkUrls" :key="'bulk-url' + idx" class="my-1" dense>
<v-col cols="12" xs="12" sm="12" md="12">
<v-text-field
v-model="bulkUrls[idx].url"
:label="$t('new-recipe.recipe-url')"
dense
single-line
validate-on-blur
autofocus
filled
hide-details
clearable
:prepend-inner-icon="$globals.icons.link"
rounded
class="rounded-lg"
>
<template #append>
<v-btn color="error" icon x-small @click="bulkUrls.splice(idx, 1)">
<v-icon>
{{ $globals.icons.delete }}
</v-icon>
</v-btn>
</template>
</v-text-field>
</v-col>
<v-col cols="12" xs="12" sm="6">
<RecipeCategoryTagSelector
v-model="bulkUrls[idx].categories"
validate-on-blur
autofocus
single-line
filled
hide-details
dense
clearable
rounded
class="rounded-lg"
></RecipeCategoryTagSelector>
</v-col>
<v-col cols="12" xs="12" sm="6">
<RecipeCategoryTagSelector
v-model="bulkUrls[idx].tags"
validate-on-blur
autofocus
tag-selector
hide-details
filled
dense
single-line
clearable
rounded
class="rounded-lg"
></RecipeCategoryTagSelector>
</v-col>
</v-row>
<v-card-actions class="justify-end">
<BaseButton delete @click="bulkUrls = []"> Clear </BaseButton>
<v-spacer></v-spacer>
<BaseButton color="info" @click="bulkUrls.push({ url: '', categories: [], tags: [] })">
<template #icon> {{ $globals.icons.createAlt }} </template> New
</BaseButton>
<BaseButton :disabled="bulkUrls.length === 0" @click="bulkCreate">
<template #icon> {{ $globals.icons.check }} </template> Submit
</BaseButton>
</v-card-actions>
</section>
</v-container>
</div>
</template>
@ -204,10 +283,12 @@ import { defineComponent, reactive, toRefs, ref, useRouter, useContext } from "@
// @ts-ignore No Types for v-jsoneditor
import VJsoneditor from "v-jsoneditor";
import { useApiSingleton } from "~/composables/use-api";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import { validators } from "~/composables/use-validators";
import { Recipe } from "~/types/api-types/recipe";
import { alert } from "~/composables/use-toast";
export default defineComponent({
components: { VJsoneditor },
components: { VJsoneditor, RecipeCategoryTagSelector },
setup() {
const state = reactive({
error: false,
@ -233,6 +314,11 @@ export default defineComponent({
text: "Import with .zip",
value: "zip",
},
{
icon: $globals.icons.link,
text: "Bulk URL Import",
value: "bulk",
},
{
icon: $globals.icons.robot,
text: "Debug Scraper",
@ -249,7 +335,6 @@ export default defineComponent({
state.loading = false;
return;
}
console.log(response);
router.push(`/recipe/${response.data}`);
}
@ -300,7 +385,6 @@ export default defineComponent({
return;
}
const { response } = await api.recipes.createOne({ name });
console.log("Create By Name Func", response);
handleResponse(response);
}
@ -318,11 +402,31 @@ export default defineComponent({
formData.append(newRecipeZipFileName, newRecipeZip.value);
const { response } = await api.upload.file("/api/recipes/create-from-zip", formData);
console.log(response);
handleResponse(response);
}
// ===================================================
// Bulk Importer
const bulkUrls = ref([{ url: "", categories: [], tags: [] }]);
async function bulkCreate() {
if (bulkUrls.value.length === 0) {
return;
}
const { response } = await api.recipes.createManyByUrl({ imports: bulkUrls.value });
if (response?.status === 202) {
alert.success("Bulk Import process has started");
} else {
alert.error("Bulk import process has failed");
}
}
return {
bulkCreate,
bulkUrls,
debugTreeView,
tabs,
domCreateByName,

View file

@ -21,7 +21,7 @@ import { useLazyRecipes } from "~/composables/use-recipes";
export default defineComponent({
components: { RecipeCardSection },
setup() {
const start = ref(1);
const start = ref(0);
const limit = ref(30);
const increment = ref(30);
const ready = ref(false);