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

chore: frontend testing setup (#1739)

* add vitest

* initialize lib w/ tests

* move to dev dep

* run tests in CI

* update file names

* move api folder to lib

* move api and api types to same folder

* update generator outpath

* rm husky

* i guess i _did_ need those types

* reorg types

* extract validators into testable components

* (WIP) start composable testing

* fix import type

* fix linter complaint

* simplify icon type def

* fix linter errors (maybe?)

* rename client file for sorting
This commit is contained in:
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

@ -0,0 +1,40 @@
import { BaseAPI } from "../base/base-clients";
import { AllBackups, BackupOptions, CreateBackup } from "~/lib/api/types/admin";
const prefix = "/api";
const routes = {
backupsAvailable: `${prefix}/backups/available`,
backupsExportDatabase: `${prefix}/backups/export/database`,
backupsUpload: `${prefix}/backups/upload`,
backupsFileNameDownload: (fileName: string) => `${prefix}/backups/${fileName}/download`,
backupsFileNameImport: (fileName: string) => `${prefix}/backups/${fileName}/import`,
backupsFileNameDelete: (fileName: string) => `${prefix}/backups/${fileName}/delete`,
};
export class BackupAPI extends BaseAPI {
/** Returns a list of available .zip files for import into Mealie.
*/
async getAll() {
return await this.requests.get<AllBackups>(routes.backupsAvailable);
}
/** Generates a backup of the recipe database in json format.
*/
async createOne(payload: CreateBackup) {
return await this.requests.post(routes.backupsExportDatabase, payload);
}
/** Import a database backup file generated from Mealie.
*/
async restoreDatabase(fileName: string, payload: BackupOptions) {
return await this.requests.post(routes.backupsFileNameImport(fileName), { name: fileName, ...payload });
}
/** Removes a database backup from the file system
*/
async deleteOne(fileName: string) {
return await this.requests.delete(routes.backupsFileNameDelete(fileName));
}
}

View file

@ -0,0 +1,25 @@
import { BaseAPI } from "../base/base-clients";
import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/group";
import { ForgotPassword } from "~/lib/api/types/user";
import { EmailTest } from "~/lib/api/types/admin";
const routes = {
base: "/api/admin/email",
forgotPassword: "/api/users/forgot-password",
invitation: "/api/groups/invitations/email",
};
export class EmailAPI extends BaseAPI {
test(payload: EmailTest) {
return this.requests.post<EmailInitationResponse>(routes.base, payload);
}
sendInvitation(payload: EmailInvitation) {
return this.requests.post<EmailInitationResponse>(routes.invitation, payload);
}
sendForgotPassword(payload: ForgotPassword) {
return this.requests.post<EmailInitationResponse>(routes.forgotPassword, payload);
}
}

View file

@ -0,0 +1,18 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
const prefix = "/api";
const routes = {
cookbooks: `${prefix}/groups/cookbooks`,
cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`,
};
export class CookbookAPI extends BaseCRUDAPI<CreateCookBook, RecipeCookBook, UpdateCookBook> {
baseRoute: string = routes.cookbooks;
itemRoute = routes.cookbooksId;
async updateAll(payload: UpdateCookBook[]) {
return await this.requests.put(this.baseRoute, payload);
}
}

View file

@ -0,0 +1,22 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
eventNotifier: `${prefix}/groups/events/notifications`,
eventNotifierId: (id: string | number) => `${prefix}/groups/events/notifications/${id}`,
};
export class GroupEventNotifierApi extends BaseCRUDAPI<
GroupEventNotifierCreate,
GroupEventNotifierOut,
GroupEventNotifierUpdate
> {
baseRoute = routes.eventNotifier;
itemRoute = routes.eventNotifierId;
async test(itemId: string) {
return await this.requests.post(`${this.baseRoute}/${itemId}/test`, {});
}
}

View file

@ -0,0 +1,14 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
const prefix = "/api";
const routes = {
rule: `${prefix}/groups/mealplans/rules`,
ruleId: (id: string | number) => `${prefix}/groups/mealplans/rules/${id}`,
};
export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesCreate, PlanRulesOut> {
baseRoute = routes.rule;
itemRoute = routes.ruleId;
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
const prefix = "/api";
const routes = {
mealplan: `${prefix}/groups/mealplans`,
random: `${prefix}/groups/mealplans/random`,
mealplanId: (id: string | number) => `${prefix}/groups/mealplans/${id}`,
};
export class MealPlanAPI extends BaseCRUDAPI<CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry> {
baseRoute = routes.mealplan;
itemRoute = routes.mealplanId;
async setRandom(payload: CreateRandomEntry) {
console.log(payload);
return await this.requests.post<ReadPlanEntry>(routes.random, payload);
}
}

View file

@ -0,0 +1,27 @@
import { BaseAPI } from "../base/base-clients";
import { ReportSummary } from "~/lib/api/types/reports";
import { SupportedMigrations } from "~/lib/api/types/group";
const prefix = "/api";
export interface MigrationPayload {
addMigrationTag: boolean;
migrationType: SupportedMigrations;
archive: File;
}
const routes = {
base: `${prefix}/groups/migrations`,
};
export class GroupMigrationApi extends BaseAPI {
async startMigration(payload: MigrationPayload) {
const form = new FormData();
form.append("add_migration_tag", String(payload.addMigrationTag));
form.append("migration_type", payload.migrationType);
form.append("archive", payload.archive);
console.log(form);
return await this.requests.post<ReportSummary>(routes.base, form);
}
}

View file

@ -0,0 +1,18 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { MultiPurposeLabelCreate, MultiPurposeLabelOut, MultiPurposeLabelUpdate } from "~/lib/api/types/labels";
const prefix = "/api";
const routes = {
labels: `${prefix}/groups/labels`,
labelsId: (id: string | number) => `${prefix}/groups/labels/${id}`,
};
export class MultiPurposeLabelsApi extends BaseCRUDAPI<
MultiPurposeLabelCreate,
MultiPurposeLabelOut,
MultiPurposeLabelUpdate
> {
baseRoute = routes.labels;
itemRoute = routes.labelsId;
}

View file

@ -0,0 +1,24 @@
import { BaseAPI } from "../base/base-clients";
import { ReportCategory, ReportOut, ReportSummary } from "~/lib/api/types/reports";
const prefix = "/api";
const routes = {
base: `${prefix}/groups/reports`,
getOne: (id: string) => `${prefix}/groups/reports/${id}`,
};
export class GroupReportsApi extends BaseAPI {
async getAll(category: ReportCategory | null) {
const query = category ? `?report_type=${category}` : "";
return await this.requests.get<ReportSummary[]>(routes.base + query);
}
async getOne(id: string) {
return await this.requests.get<ReportOut>(routes.getOne(id));
}
async deleteOne(id: string) {
return await this.requests.delete(routes.getOne(id));
}
}

View file

@ -0,0 +1,26 @@
import { BaseAPI } from "../base/base-clients";
import { SuccessResponse } from "~/lib/api/types/response";
import { SeederConfig } from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
base: `${prefix}/groups/seeders`,
foods: `${prefix}/groups/seeders/foods`,
units: `${prefix}/groups/seeders/units`,
labels: `${prefix}/groups/seeders/labels`,
};
export class GroupDataSeederApi extends BaseAPI {
foods(payload: SeederConfig) {
return this.requests.post<SuccessResponse>(routes.foods, payload);
}
units(payload: SeederConfig) {
return this.requests.post<SuccessResponse>(routes.units, payload);
}
labels(payload: SeederConfig) {
return this.requests.post<SuccessResponse>(routes.labels, payload);
}
}

View file

@ -0,0 +1,67 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
import {
ShoppingListCreate,
ShoppingListItemCreate,
ShoppingListItemOut,
ShoppingListItemUpdate,
ShoppingListOut,
ShoppingListUpdate,
} from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
shoppingLists: `${prefix}/groups/shopping/lists`,
shoppingListsId: (id: string) => `${prefix}/groups/shopping/lists/${id}`,
shoppingListIdAddRecipe: (id: string, recipeId: string) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}`,
shoppingListItems: `${prefix}/groups/shopping/items`,
shoppingListItemsId: (id: string) => `${prefix}/groups/shopping/items/${id}`,
};
export class ShoppingListsApi extends BaseCRUDAPI<ShoppingListCreate, ShoppingListOut, ShoppingListUpdate> {
baseRoute = routes.shoppingLists;
itemRoute = routes.shoppingListsId;
async addRecipe(itemId: string, recipeId: string) {
return await this.requests.post(routes.shoppingListIdAddRecipe(itemId, recipeId), {});
}
async removeRecipe(itemId: string, recipeId: string) {
return await this.requests.delete(routes.shoppingListIdAddRecipe(itemId, recipeId));
}
}
export class ShoppingListItemsApi extends BaseCRUDAPI<
ShoppingListItemCreate,
ShoppingListItemOut,
ShoppingListItemUpdate
> {
baseRoute = routes.shoppingListItems;
itemRoute = routes.shoppingListItemsId;
async updateMany(items: ShoppingListItemOut[]) {
return await this.requests.put(routes.shoppingListItems, items);
}
async deleteMany(items: ShoppingListItemOut[]) {
let query = "?";
items.forEach((item) => {
query += `ids=${item.id}&`;
});
return await this.requests.delete(routes.shoppingListItems + query);
}
}
export class ShoppingApi {
public lists: ShoppingListsApi;
public items: ShoppingListItemsApi;
constructor(requests: ApiRequestInstance) {
this.lists = new ShoppingListsApi(requests);
this.items = new ShoppingListItemsApi(requests);
}
}

View file

@ -0,0 +1,13 @@
import { BaseAPI } from "../base/base-clients";
import { ServerTask } from "~/lib/api/types/server";
const prefix = "/api";
const routes = {
base: `${prefix}/groups/server-tasks`,
};
export class GroupServerTaskAPI extends BaseAPI {
async getAll() {
return await this.requests.get<ServerTask[]>(routes.base);
}
}

View file

@ -0,0 +1,14 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateWebhook, ReadWebhook } from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
webhooks: `${prefix}/groups/webhooks`,
webhooksId: (id: string | number) => `${prefix}/groups/webhooks/${id}`,
};
export class WebhooksAPI extends BaseCRUDAPI<CreateWebhook, ReadWebhook> {
baseRoute = routes.webhooks;
itemRoute = routes.webhooksId;
}

View file

@ -0,0 +1,78 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CategoryBase, GroupBase, GroupInDB, UserOut } from "~/lib/api/types/user";
import {
CreateInviteToken,
GroupAdminUpdate,
GroupStatistics,
GroupStorage,
ReadGroupPreferences,
ReadInviteToken,
SetPermissions,
UpdateGroupPreferences,
} from "~/lib/api/types/group";
const prefix = "/api";
const routes = {
groups: `${prefix}/admin/groups`,
groupsSelf: `${prefix}/groups/self`,
categories: `${prefix}/groups/categories`,
members: `${prefix}/groups/members`,
permissions: `${prefix}/groups/permissions`,
preferences: `${prefix}/groups/preferences`,
statistics: `${prefix}/groups/statistics`,
storage: `${prefix}/groups/storage`,
invitation: `${prefix}/groups/invitations`,
groupsId: (id: string | number) => `${prefix}/admin/groups/${id}`,
};
export class GroupAPI extends BaseCRUDAPI<GroupBase, GroupInDB, GroupAdminUpdate> {
baseRoute = routes.groups;
itemRoute = routes.groupsId;
/** Returns the Group Data for the Current User
*/
async getCurrentUserGroup() {
return await this.requests.get<GroupInDB>(routes.groupsSelf);
}
async getCategories() {
return await this.requests.get<CategoryBase[]>(routes.categories);
}
async setCategories(payload: CategoryBase[]) {
return await this.requests.put<CategoryBase[]>(routes.categories, payload);
}
async getPreferences() {
return await this.requests.get<ReadGroupPreferences>(routes.preferences);
}
async setPreferences(payload: UpdateGroupPreferences) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<ReadGroupPreferences, UpdateGroupPreferences>(routes.preferences, payload);
}
async createInvitation(payload: CreateInviteToken) {
return await this.requests.post<ReadInviteToken>(routes.invitation, payload);
}
async fetchMembers() {
return await this.requests.get<UserOut[]>(routes.members);
}
async setMemberPermissions(payload: SetPermissions) {
// TODO: This should probably be a patch request, which isn't offered by the API currently
return await this.requests.put<UserOut, SetPermissions>(routes.permissions, payload);
}
async statistics() {
return await this.requests.get<GroupStatistics>(routes.statistics);
}
async storage() {
return await this.requests.get<GroupStorage>(routes.storage);
}
}

View file

@ -0,0 +1,16 @@
import { BaseAPI } from "../base/base-clients";
const prefix = "/api";
export class OcrAPI extends BaseAPI {
// Currently unused in favor for the endpoint using asset names
async fileToTsv(file: File) {
const formData = new FormData();
formData.append("file", file);
return await this.requests.post(`${prefix}/ocr/file-to-tsv`, formData);
}
async assetToTsv(recipeSlug: string, assetName: string) {
return await this.requests.post(`${prefix}/ocr/asset-to-tsv`, { recipeSlug, assetName });
}
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
import { CategoryIn, RecipeCategoryResponse } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
const routes = {
categories: `${prefix}/categories`,
categoriesId: (category: string) => `${prefix}/categories/${category}`,
categoriesSlug: (category: string) => `${prefix}/categories/slug/${category}`,
};
export class CategoriesAPI extends BaseCRUDAPI<CategoryIn, RecipeCategoryResponse> {
baseRoute: string = routes.categories;
itemRoute = routes.categoriesId;
async bySlug(slug: string) {
return await this.requests.get<RecipeCategoryResponse>(routes.categoriesSlug(slug));
}
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
import { RecipeTagResponse, TagIn } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
const routes = {
tags: `${prefix}/tags`,
tagsId: (tag: string) => `${prefix}/tags/${tag}`,
tagsSlug: (tag: string) => `${prefix}/tags/slug/${tag}`,
};
export class TagsAPI extends BaseCRUDAPI<TagIn, RecipeTagResponse> {
baseRoute: string = routes.tags;
itemRoute = routes.tagsId;
async bySlug(slug: string) {
return await this.requests.get<RecipeTagResponse>(routes.tagsSlug(slug));
}
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
import { RecipeTool, RecipeToolCreate, RecipeToolResponse } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
const routes = {
tools: `${prefix}/tools`,
toolsId: (id: string) => `${prefix}/tools/${id}`,
toolsSlug: (id: string) => `${prefix}/tools/slug/${id}`,
};
export class ToolsApi extends BaseCRUDAPI<RecipeToolCreate, RecipeTool> {
baseRoute: string = routes.tools;
itemRoute = routes.toolsId;
async bySlug(slug: string) {
return await this.requests.get<RecipeToolResponse>(routes.toolsSlug(slug));
}
}

View file

@ -0,0 +1,48 @@
import { BaseAPI } from "../base/base-clients";
import { AssignCategories, AssignSettings, AssignTags, DeleteRecipes, ExportRecipes } from "~/lib/api/types/recipe";
import { GroupDataExport } from "~/lib/api/types/group";
// Many bulk actions return nothing
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface BulkActionResponse {}
const prefix = "/api";
const routes = {
bulkExport: prefix + "/recipes/bulk-actions/export",
purgeExports: prefix + "/recipes/bulk-actions/export/purge",
bulkCategorize: prefix + "/recipes/bulk-actions/categorize",
bulkTag: prefix + "/recipes/bulk-actions/tag",
bulkDelete: prefix + "/recipes/bulk-actions/delete",
bulkSettings: prefix + "/recipes/bulk-actions/settings",
};
export class BulkActionsAPI extends BaseAPI {
async bulkExport(payload: ExportRecipes) {
return await this.requests.post<BulkActionResponse>(routes.bulkExport, payload);
}
async bulkCategorize(payload: AssignCategories) {
return await this.requests.post<BulkActionResponse>(routes.bulkCategorize, payload);
}
async bulkSetSettings(payload: AssignSettings) {
return await this.requests.post<BulkActionResponse>(routes.bulkSettings, payload);
}
async bulkTag(payload: AssignTags) {
return await this.requests.post<BulkActionResponse>(routes.bulkTag, payload);
}
async bulkDelete(payload: DeleteRecipes) {
return await this.requests.post<BulkActionResponse>(routes.bulkDelete, payload);
}
async fetchExports() {
return await this.requests.get<GroupDataExport[]>(routes.bulkExport);
}
async purgeExports() {
return await this.requests.delete<BulkActionResponse>(routes.purgeExports);
}
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateIngredientFood, IngredientFood } from "~/lib/api/types/recipe";
const prefix = "/api";
const routes = {
food: `${prefix}/foods`,
foodsFood: (tag: string) => `${prefix}/foods/${tag}`,
merge: `${prefix}/foods/merge`,
};
export class FoodAPI extends BaseCRUDAPI<CreateIngredientFood, IngredientFood> {
baseRoute: string = routes.food;
itemRoute = routes.foodsFood;
merge(fromId: string, toId: string) {
// @ts-ignore TODO: fix this
return this.requests.put<IngredientFood>(routes.merge, { fromFood: fromId, toFood: toId });
}
}

View file

@ -0,0 +1,20 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { CreateIngredientUnit, IngredientUnit } from "~/lib/api/types/recipe";
const prefix = "/api";
const routes = {
unit: `${prefix}/units`,
unitsUnit: (tag: string) => `${prefix}/units/${tag}`,
merge: `${prefix}/units/merge`,
};
export class UnitAPI extends BaseCRUDAPI<CreateIngredientUnit, IngredientUnit> {
baseRoute: string = routes.unit;
itemRoute = routes.unitsUnit;
merge(fromId: string, toId: string) {
// @ts-ignore TODO: fix this
return this.requests.put<IngredientUnit>(routes.merge, { fromUnit: fromId, toUnit: toId });
}
}

View file

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

View file

@ -0,0 +1,19 @@
import { BaseCRUDAPI } from "../../base/base-clients";
import { RecipeCommentCreate, RecipeCommentOut, RecipeCommentUpdate } from "~/lib/api/types/recipe";
const prefix = "/api";
const routes = {
comment: `${prefix}/comments`,
byRecipe: (id: string) => `${prefix}/recipes/${id}/comments`,
commentsId: (id: string) => `${prefix}/comments/${id}`,
};
export class CommentsApi extends BaseCRUDAPI<RecipeCommentCreate, RecipeCommentOut, RecipeCommentUpdate> {
baseRoute: string = routes.comment;
itemRoute = routes.commentsId;
async byRecipe(slug: string) {
return await this.requests.get<RecipeCommentOut[]>(routes.byRecipe(slug));
}
}

View file

@ -0,0 +1,14 @@
import { BaseCRUDAPI } from "../../base/base-clients";
import { RecipeShareToken, RecipeShareTokenCreate } from "~/lib/api/types/recipe";
const prefix = "/api";
const routes = {
shareToken: `${prefix}/shared/recipes`,
shareTokenId: (id: string) => `${prefix}/shared/recipes/${id}`,
};
export class RecipeShareApi extends BaseCRUDAPI<RecipeShareTokenCreate, RecipeShareToken> {
baseRoute: string = routes.shareToken;
itemRoute = routes.shareTokenId;
}

View file

@ -0,0 +1,129 @@
import { BaseCRUDAPI } from "../../base/base-clients";
import { CommentsApi } from "./recipe-comments";
import { RecipeShareApi } from "./recipe-share";
import {
Recipe,
CreateRecipe,
RecipeAsset,
CreateRecipeByUrlBulk,
ParsedIngredient,
UpdateImageResponse,
RecipeZipTokenResponse,
} from "~/lib/api/types/recipe";
import { ApiRequestInstance } from "~/lib/api/types/non-generated";
export type Parser = "nlp" | "brute";
export interface CreateAsset {
name: string;
icon: string;
extension: string;
file: File;
}
const prefix = "/api";
const routes = {
recipesCreate: `${prefix}/recipes/create`,
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`,
recipesParseIngredients: `${prefix}/parser/ingredients`,
recipesCreateFromOcr: `${prefix}/recipes/create-ocr`,
recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`,
recipesRecipeSlugExport: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/exports`,
recipesRecipeSlugExportZip: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/exports/zip`,
recipesRecipeSlugImage: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/image`,
recipesRecipeSlugAssets: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/assets`,
recipesSlugComments: (slug: string) => `${prefix}/recipes/${slug}/comments`,
recipesSlugCommentsId: (slug: string, id: number) => `${prefix}/recipes/${slug}/comments/${id}`,
};
export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
baseRoute: string = routes.recipesBase;
itemRoute = routes.recipesRecipeSlug;
comments: CommentsApi;
share: RecipeShareApi;
constructor(requests: ApiRequestInstance) {
super(requests);
this.comments = new CommentsApi(requests);
this.share = new RecipeShareApi(requests);
}
async getAllByCategory(categories: string[]) {
return await this.requests.get<Recipe[]>(routes.recipesCategory, {
categories,
});
}
async createAsset(recipeSlug: string, payload: CreateAsset) {
const formData = new FormData();
formData.append("file", payload.file);
formData.append("name", payload.name);
formData.append("extension", payload.extension);
formData.append("icon", payload.icon);
return await this.requests.post<RecipeAsset>(routes.recipesRecipeSlugAssets(recipeSlug), formData);
}
updateImage(slug: string, fileObject: File) {
const formData = new FormData();
formData.append("image", fileObject);
formData.append("extension", fileObject.name.split(".").pop() ?? "");
return this.requests.put<UpdateImageResponse, FormData>(routes.recipesRecipeSlugImage(slug), formData);
}
updateImagebyURL(slug: string, url: string) {
return this.requests.post<UpdateImageResponse>(routes.recipesRecipeSlugImage(slug), { url });
}
async testCreateOneUrl(url: string) {
return await this.requests.post<Recipe | null>(routes.recipesTestScrapeUrl, { url });
}
async createOneByUrl(url: string, includeTags: boolean) {
return await this.requests.post<string>(routes.recipesCreateUrl, { url, includeTags });
}
async createManyByUrl(payload: CreateRecipeByUrlBulk) {
return await this.requests.post<string>(routes.recipesCreateUrlBulk, payload);
}
async parseIngredients(parser: Parser, ingredients: Array<string>) {
parser = parser || "nlp";
return await this.requests.post<ParsedIngredient[]>(routes.recipesParseIngredients, { parser, ingredients });
}
async parseIngredient(parser: Parser, ingredient: string) {
parser = parser || "nlp";
return await this.requests.post<ParsedIngredient>(routes.recipesParseIngredient, { parser, ingredient });
}
async getZipToken(recipeSlug: string) {
return await this.requests.post<RecipeZipTokenResponse>(routes.recipesRecipeSlugExport(recipeSlug), {});
}
getZipRedirectUrl(recipeSlug: string, token: string) {
return `${routes.recipesRecipeSlugExportZip(recipeSlug)}?token=${token}`;
}
async createFromOcr(file: File, makeFileRecipeImage: boolean) {
const formData = new FormData();
formData.append("file", file);
formData.append("extension", file.name.split(".").pop() ?? "");
formData.append("makefilerecipeimage", String(makeFileRecipeImage));
return await this.requests.post(routes.recipesCreateFromOcr, formData);
}
}

View file

@ -0,0 +1,7 @@
import { BaseAPI } from "../base/base-clients";
export class UploadFile extends BaseAPI {
file(url: string, fileObject: any) {
return this.requests.post<string>(url, fileObject);
}
}

View file

@ -0,0 +1,16 @@
import { BaseAPI } from "../base/base-clients";
import { CreateUserRegistration } from "~/lib/api/types/user";
const prefix = "/api";
const routes = {
register: `${prefix}/users/register`,
};
export class RegisterAPI extends BaseAPI {
/** Returns a list of available .zip files for import into Mealie.
*/
async register(payload: CreateUserRegistration) {
return await this.requests.post<any>(routes.register, payload);
}
}

View file

@ -0,0 +1,68 @@
import { BaseCRUDAPI } from "../base/base-clients";
import {
ChangePassword,
DeleteTokenResponse,
LongLiveTokenIn,
LongLiveTokenOut,
ResetPassword,
UserBase,
UserFavorites,
UserIn,
UserOut,
} from "~/lib/api/types/user";
const prefix = "/api";
const routes = {
usersSelf: `${prefix}/users/self`,
passwordReset: `${prefix}/users/reset-password`,
passwordChange: `${prefix}/users/password`,
users: `${prefix}/users`,
usersIdImage: (id: string) => `${prefix}/users/${id}/image`,
usersIdResetPassword: (id: string) => `${prefix}/users/${id}/reset-password`,
usersId: (id: string) => `${prefix}/users/${id}`,
usersIdFavorites: (id: string) => `${prefix}/users/${id}/favorites`,
usersIdFavoritesSlug: (id: string, slug: string) => `${prefix}/users/${id}/favorites/${slug}`,
usersApiTokens: `${prefix}/users/api-tokens`,
usersApiTokensTokenId: (token_id: string | number) => `${prefix}/users/api-tokens/${token_id}`,
};
export class UserApi extends BaseCRUDAPI<UserIn, UserOut, UserBase> {
baseRoute: string = routes.users;
itemRoute = (itemid: string) => routes.usersId(itemid);
async addFavorite(id: string, slug: string) {
return await this.requests.post(routes.usersIdFavoritesSlug(id, slug), {});
}
async removeFavorite(id: string, slug: string) {
return await this.requests.delete(routes.usersIdFavoritesSlug(id, slug));
}
async getFavorites(id: string) {
return await this.requests.get<UserFavorites>(routes.usersIdFavorites(id));
}
async changePassword(changePassword: ChangePassword) {
return await this.requests.put(routes.passwordChange, changePassword);
}
async createAPIToken(tokenName: LongLiveTokenIn) {
return await this.requests.post<LongLiveTokenOut>(routes.usersApiTokens, tokenName);
}
async deleteAPIToken(tokenId: number) {
return await this.requests.delete<DeleteTokenResponse>(routes.usersApiTokensTokenId(tokenId));
}
userProfileImage(id: string) {
if (!id || id === undefined) return;
return `/api/users/${id}/image`;
}
async resetPassword(payload: ResetPassword) {
return await this.requests.post(routes.passwordReset, payload);
}
}

View file

@ -0,0 +1,20 @@
import { BaseAPI } from "../base/base-clients";
import { FileTokenResponse } from "~/lib/api/types/response";
const prefix = "/api";
export class UtilsAPI extends BaseAPI {
async download(url: string) {
const { response } = await this.requests.get<FileTokenResponse>(url);
if (!response) {
return;
}
const token: string = response.data.fileToken;
const tokenURL = prefix + "/utils/download?token=" + token;
window.open(tokenURL, "_blank");
return response;
}
}