added ability to mark recipes as private

This commit is contained in:
vabene1111 2022-07-13 15:46:39 +02:00
parent 51076d4ced
commit e91790f5ac
10 changed files with 224 additions and 56 deletions

View File

@ -299,6 +299,27 @@ class CustomIsShare(permissions.BasePermission):
return False return False
class CustomRecipePermission(permissions.BasePermission):
"""
Custom permission class for recipe api endpoint
"""
message = _('You do not have the required permissions to view this page!')
def has_permission(self, request, view): # user is either at least a guest or a share link is given and the request is safe
share = request.query_params.get('share', None)
return has_group_permission(request.user, ['guest']) or (share and request.method in SAFE_METHODS and 'pk' in view.kwargs)
def has_object_permission(self, request, view, obj):
share = request.query_params.get('share', None)
if share:
return share_link_valid(obj, share)
else:
if obj.private:
return ((obj.created_by == request.user) or (request.user in obj.shared.all())) and obj.space == request.space
else:
return has_group_permission(request.user, ['guest']) and obj.space == request.space
def above_space_limit(space): # TODO add file storage limit def above_space_limit(space): # TODO add file storage limit
""" """
Test if the space has reached any limit (e.g. max recipes, users, ..) Test if the space has reached any limit (e.g. max recipes, users, ..)

View File

@ -0,0 +1,25 @@
# Generated by Django 4.0.6 on 2022-07-13 10:53
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0178_remove_userpreference_search_style_and_more'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='private',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='recipe',
name='shared',
field=models.ManyToManyField(blank=True, related_name='recipe_shared_with', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -738,6 +738,8 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
internal = models.BooleanField(default=False) internal = models.BooleanField(default=False)
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE) nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
show_ingredient_overview = models.BooleanField(default=True) show_ingredient_overview = models.BooleanField(default=True)
private = models.BooleanField(default=False)
shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with')
source_url = models.CharField(max_length=1024, default=None, blank=True, null=True) source_url = models.CharField(max_length=1024, default=None, blank=True, null=True)
created_by = models.ForeignKey(User, on_delete=models.PROTECT) created_by = models.ForeignKey(User, on_delete=models.PROTECT)

View File

@ -5,7 +5,7 @@ from gettext import gettext as _
from html import escape from html import escape
from smtplib import SMTPException from smtplib import SMTPException
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User, AnonymousUser
from django.core.mail import send_mail from django.core.mail import send_mail
from django.db.models import Avg, Q, QuerySet, Sum from django.db.models import Avg, Q, QuerySet, Sum
from django.http import BadHeaderError from django.http import BadHeaderError
@ -124,7 +124,10 @@ class SpaceFilterSerializer(serializers.ListSerializer):
# if query is sliced it came from api request not nested serializer # if query is sliced it came from api request not nested serializer
return super().to_representation(data) return super().to_representation(data)
if self.child.Meta.model == User: if self.child.Meta.model == User:
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all() if type(self.context['request'].user) == AnonymousUser:
data = []
else:
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all()
else: else:
data = data.filter(**{'__'.join(data.model.get_space_key()): self.context['request'].space}) data = data.filter(**{'__'.join(data.model.get_space_key()): self.context['request'].space})
return super().to_representation(data) return super().to_representation(data)
@ -732,6 +735,7 @@ class RecipeSerializer(RecipeBaseSerializer):
keywords = KeywordSerializer(many=True) keywords = KeywordSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating') rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked') last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
shared = UserNameSerializer(many=True)
class Meta: class Meta:
model = Recipe model = Recipe
@ -739,6 +743,7 @@ class RecipeSerializer(RecipeBaseSerializer):
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', 'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked', 'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
'private', 'shared',
) )
read_only_fields = ['image', 'created_by', 'created_at'] read_only_fields = ['image', 'created_by', 'created_at']

View File

@ -1,6 +1,7 @@
import json import json
import pytest import pytest
from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
@ -30,6 +31,7 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 1 assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 1
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0 assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0
# test for space filter
with scopes_disabled(): with scopes_disabled():
recipe_1_s1.space = space_2 recipe_1_s1.space = space_2
recipe_1_s1.save() recipe_1_s1.save()
@ -37,8 +39,23 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0 assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 1 assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 1
# test for private recipe filter
with scopes_disabled():
recipe_1_s1.created_by = auth.get_user(u1_s1)
recipe_1_s1.private = True
recipe_1_s1.save()
def test_share_permission(recipe_1_s1, u1_s1, u1_s2, a_u): assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0
with scopes_disabled():
recipe_1_s1.created_by = auth.get_user(u1_s2)
recipe_1_s1.save()
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 1
def test_share_permission(recipe_1_s1, u1_s1, u1_s2, u2_s1, a_u):
assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk])).status_code == 200 assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk])).status_code == 200
assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk])).status_code == 404 assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk])).status_code == 404
@ -52,6 +69,15 @@ def test_share_permission(recipe_1_s1, u1_s1, u1_s2, a_u):
assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200 assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 404 # TODO fix in https://github.com/TandoorRecipes/recipes/issues/1238 assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 404 # TODO fix in https://github.com/TandoorRecipes/recipes/issues/1238
recipe_1_s1.created_by = auth.get_user(u1_s1)
recipe_1_s1.private = True
recipe_1_s1.save()
assert a_u.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u2_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u2_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk])).status_code == 403
@pytest.mark.parametrize("arg", [ @pytest.mark.parametrize("arg", [
['a_u', 403], ['a_u', 403],
@ -80,6 +106,22 @@ def test_update(arg, request, recipe_1_s1):
validate_recipe(j, json.loads(r.content)) validate_recipe(j, json.loads(r.content))
def test_update_private_recipe(u1_s1, u2_s1, recipe_1_s1):
r = u1_s1.patch(reverse(DETAIL_URL, args={recipe_1_s1.id}), {'name': 'test1'}, content_type='application/json')
assert r.status_code == 200
with scopes_disabled():
recipe_1_s1.private = True
recipe_1_s1.created_by = auth.get_user(u1_s1)
recipe_1_s1.save()
r = u1_s1.patch(reverse(DETAIL_URL, args={recipe_1_s1.id}), {'name': 'test2'}, content_type='application/json')
assert r.status_code == 200
r = u2_s1.patch(reverse(DETAIL_URL, args={recipe_1_s1.id}), {'name': 'test3'}, content_type='application/json')
assert r.status_code == 403
@pytest.mark.parametrize("arg", [ @pytest.mark.parametrize("arg", [
['a_u', 403], ['a_u', 403],
['g1_s1', 201], ['g1_s1', 201],
@ -107,22 +149,22 @@ def test_add(arg, request, u1_s2):
x += 1 x += 1
def test_delete(u1_s1, u1_s2, recipe_1_s1): def test_delete(u1_s1, u1_s2, u2_s1, recipe_1_s1, recipe_2_s1):
with scopes_disabled(): with scopes_disabled():
r = u1_s2.delete( r = u1_s2.delete(reverse(DETAIL_URL, args={recipe_1_s1.id}))
reverse(
DETAIL_URL,
args={recipe_1_s1.id}
)
)
assert r.status_code == 404 assert r.status_code == 404
r = u1_s1.delete( r = u1_s1.delete(reverse(DETAIL_URL, args={recipe_1_s1.id}))
reverse(
DETAIL_URL,
args={recipe_1_s1.id}
)
)
assert r.status_code == 204 assert r.status_code == 204
assert not Recipe.objects.filter(pk=recipe_1_s1.id).exists() assert not Recipe.objects.filter(pk=recipe_1_s1.id).exists()
recipe_2_s1.created_by = auth.get_user(u1_s1)
recipe_2_s1.private = True
recipe_2_s1.save()
r = u2_s1.delete(reverse(DETAIL_URL, args={recipe_2_s1.id}))
assert r.status_code == 403
r = u1_s1.delete(reverse(DETAIL_URL, args={recipe_2_s1.id}))
assert r.status_code == 204

View File

@ -53,7 +53,7 @@ from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner, from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, CustomIsOwner,
CustomIsOwnerReadOnly, CustomIsShare, CustomIsShared, CustomIsOwnerReadOnly, CustomIsShare, CustomIsShared,
CustomIsSpaceOwner, CustomIsUser, group_required, CustomIsSpaceOwner, CustomIsUser, group_required,
is_space_owner, switch_user_active_space, above_space_limit) is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission)
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup
from cookbook.helper.scrapers.scrapers import text_scraper from cookbook.helper.scrapers.scrapers import text_scraper
@ -715,7 +715,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
queryset = Recipe.objects queryset = Recipe.objects
serializer_class = RecipeSerializer serializer_class = RecipeSerializer
# TODO split read and write permission for meal plan guest # TODO split read and write permission for meal plan guest
permission_classes = [CustomIsShare | CustomIsGuest] permission_classes = [CustomRecipePermission]
pagination_class = RecipePagination pagination_class = RecipePagination
query_params = [ query_params = [
@ -782,13 +782,14 @@ class RecipeViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
share = self.request.query_params.get('share', None) share = self.request.query_params.get('share', None)
if self.detail: if self.detail: # if detail request and not list, private condition is verified by permission class
if not share: if not share: # filter for space only if not shared
self.queryset = self.queryset.filter(space=self.request.space) self.queryset = self.queryset.filter(space=self.request.space)
return super().get_queryset() return super().get_queryset()
if not (share and self.detail): self.queryset = self.queryset.filter(space=self.request.space).filter(
self.queryset = self.queryset.filter(space=self.request.space) Q(private=False) | (Q(private=True) & (Q(created_by=self.request.user) | Q(shared=self.request.user)))
)
params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x
in list(self.request.GET)} in list(self.request.GET)}
@ -803,8 +804,6 @@ class RecipeViewSet(viewsets.ModelViewSet):
}) })
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
# TODO write extensive tests for permissions
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'list': if self.action == 'list':
return RecipeOverviewSerializer return RecipeOverviewSerializer

View File

@ -188,10 +188,31 @@
</b-form-checkbox> </b-form-checkbox>
<br/> <br/>
<label for="id_name"> {{ $t("Imported_From") }}</label> <label> {{ $t("Imported_From") }}</label>
<b-form-input v-model="recipe.source_url"> <b-form-input v-model="recipe.source_url">
</b-form-input> </b-form-input>
<br/>
<label> {{ $t("Private_Recipe") }}</label>
<b-form-checkbox v-model="recipe.private">
{{ $t('Private_Recipe_Help') }}
</b-form-checkbox>
<br/>
<label> {{ $t("Share") }}</label>
<generic-multiselect
@change="recipe.shared = $event.val"
parent_variable="recipe.shared"
:initial_selection="recipe.shared"
:label="'username'"
:model="Models.USER_NAME"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
v-bind:placeholder="$t('Share')"
:limit="25"
></generic-multiselect>
</b-collapse> </b-collapse>
</div> </div>
</div> </div>
@ -723,6 +744,7 @@ import GenericModalForm from "@/components/Modals/GenericModalForm"
import mavonEditor from 'mavon-editor' import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css' import 'mavon-editor/dist/css/index.css'
import _debounce from "lodash/debounce"; import _debounce from "lodash/debounce";
import GenericMultiselect from "@/components/GenericMultiselect";
// use // use
Vue.use(mavonEditor) Vue.use(mavonEditor)
@ -731,7 +753,7 @@ Vue.use(BootstrapVue)
export default { export default {
name: "RecipeEditView", name: "RecipeEditView",
mixins: [ResolveUrlMixin, ApiMixin], mixins: [ResolveUrlMixin, ApiMixin],
components: {Multiselect, LoadingSpinner, draggable, GenericModalForm}, components: {Multiselect, LoadingSpinner, draggable, GenericModalForm, GenericMultiselect},
data() { data() {
return { return {
recipe_id: window.RECIPE_ID, recipe_id: window.RECIPE_ID,

View File

@ -68,6 +68,10 @@
"Enable_Amount": "Enable Amount", "Enable_Amount": "Enable Amount",
"Disable_Amount": "Disable Amount", "Disable_Amount": "Disable Amount",
"Ingredient Editor": "Ingredient Editor", "Ingredient Editor": "Ingredient Editor",
"Private_Recipe": "Private Recipe",
"Private_Recipe_Help": "Recipe is only shown to you and people its shared with.",
"Add_Step": "Add Step", "Add_Step": "Add Step",
"Keywords": "Keywords", "Keywords": "Keywords",
"Books": "Books", "Books": "Books",

View File

@ -8,7 +8,7 @@ axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
export function apiLoadRecipe(recipe_id) { export function apiLoadRecipe(recipe_id) {
let url = resolveDjangoUrl('api:recipe-detail', recipe_id) let url = resolveDjangoUrl('api:recipe-detail', recipe_id)
if (window.SHARE_UID !== undefined) { if (window.SHARE_UID !== undefined && window.SHARE_UID !== 'None') {
url += '?share=' + window.SHARE_UID url += '?share=' + window.SHARE_UID
} }

View File

@ -2023,6 +2023,12 @@ export interface RecipeBookFilter {
* @interface RecipeFile * @interface RecipeFile
*/ */
export interface RecipeFile { export interface RecipeFile {
/**
*
* @type {number}
* @memberof RecipeFile
*/
id?: number;
/** /**
* *
* @type {string} * @type {string}
@ -2031,16 +2037,16 @@ export interface RecipeFile {
name: string; name: string;
/** /**
* *
* @type {any} * @type {string}
* @memberof RecipeFile * @memberof RecipeFile
*/ */
file?: any; file_download?: string;
/** /**
* *
* @type {number} * @type {string}
* @memberof RecipeFile * @memberof RecipeFile
*/ */
id?: number; preview?: string;
} }
/** /**
* *
@ -3540,18 +3546,6 @@ export interface UserPreference {
* @memberof UserPreference * @memberof UserPreference
*/ */
use_kj?: boolean; use_kj?: boolean;
/**
*
* @type {string}
* @memberof UserPreference
*/
search_style?: UserPreferenceSearchStyleEnum;
/**
*
* @type {boolean}
* @memberof UserPreference
*/
show_recent?: boolean;
/** /**
* *
* @type {Array<CustomFilterShared>} * @type {Array<CustomFilterShared>}
@ -3690,15 +3684,6 @@ export enum UserPreferenceDefaultPageEnum {
Plan = 'PLAN', Plan = 'PLAN',
Books = 'BOOKS' Books = 'BOOKS'
} }
/**
* @export
* @enum {string}
*/
export enum UserPreferenceSearchStyleEnum {
Small = 'SMALL',
Large = 'LARGE',
New = 'NEW'
}
/** /**
* *
@ -4713,7 +4698,40 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
}; };
}, },
/** /**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json, original html,json and images * function to handle files passed by application importer
* @param {any} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createimportFiles: async (body?: any, options: any = {}): Promise<RequestArgs> => {
const localVarPath = `/api/import/`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json and images
* @param {any} [body] * @param {any} [body]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
@ -11014,7 +11032,17 @@ export const ApiApiFp = function(configuration?: Configuration) {
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json, original html,json and images * function to handle files passed by application importer
* @param {any} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createimportFiles(body?: any, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<any>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createimportFiles(body, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json and images
* @param {any} [body] * @param {any} [body]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
@ -13042,7 +13070,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
return localVarFp.createViewLog(viewLog, options).then((request) => request(axios, basePath)); return localVarFp.createViewLog(viewLog, options).then((request) => request(axios, basePath));
}, },
/** /**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json, original html,json and images * function to handle files passed by application importer
* @param {any} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createimportFiles(body?: any, options?: any): AxiosPromise<any> {
return localVarFp.createimportFiles(body, options).then((request) => request(axios, basePath));
},
/**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json and images
* @param {any} [body] * @param {any} [body]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
@ -14958,7 +14995,18 @@ export class ApiApi extends BaseAPI {
} }
/** /**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json, original html,json and images * function to handle files passed by application importer
* @param {any} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ApiApi
*/
public createimportFiles(body?: any, options?: any) {
return ApiApiFp(this.configuration).createimportFiles(body, options).then((request) => request(this.axios, this.basePath));
}
/**
* function to retrieve a recipe from a given url or source string :param request: standard request with additional post parameters - url: url to use for importing recipe - data: if no url is given recipe is imported from provided source data - (optional) bookmarklet: id of bookmarklet import to use, overrides URL and data attributes :return: JsonResponse containing the parsed json and images
* @param {any} [body] * @param {any} [body]
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}