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:
parent
9f6bcc83d5
commit
fcc5d99d40
182 changed files with 902 additions and 487 deletions
40
frontend/lib/api/user/backups.ts
Normal file
40
frontend/lib/api/user/backups.ts
Normal 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));
|
||||
}
|
||||
}
|
25
frontend/lib/api/user/email.ts
Normal file
25
frontend/lib/api/user/email.ts
Normal 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);
|
||||
}
|
||||
}
|
18
frontend/lib/api/user/group-cookbooks.ts
Normal file
18
frontend/lib/api/user/group-cookbooks.ts
Normal 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);
|
||||
}
|
||||
}
|
22
frontend/lib/api/user/group-event-notifier.ts
Normal file
22
frontend/lib/api/user/group-event-notifier.ts
Normal 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`, {});
|
||||
}
|
||||
}
|
14
frontend/lib/api/user/group-mealplan-rules.ts
Normal file
14
frontend/lib/api/user/group-mealplan-rules.ts
Normal 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;
|
||||
}
|
20
frontend/lib/api/user/group-mealplan.ts
Normal file
20
frontend/lib/api/user/group-mealplan.ts
Normal 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);
|
||||
}
|
||||
}
|
27
frontend/lib/api/user/group-migrations.ts
Normal file
27
frontend/lib/api/user/group-migrations.ts
Normal 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);
|
||||
}
|
||||
}
|
18
frontend/lib/api/user/group-multiple-purpose-labels.ts
Normal file
18
frontend/lib/api/user/group-multiple-purpose-labels.ts
Normal 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;
|
||||
}
|
24
frontend/lib/api/user/group-reports.ts
Normal file
24
frontend/lib/api/user/group-reports.ts
Normal 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));
|
||||
}
|
||||
}
|
26
frontend/lib/api/user/group-seeder.ts
Normal file
26
frontend/lib/api/user/group-seeder.ts
Normal 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);
|
||||
}
|
||||
}
|
67
frontend/lib/api/user/group-shopping-lists.ts
Normal file
67
frontend/lib/api/user/group-shopping-lists.ts
Normal 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);
|
||||
}
|
||||
}
|
13
frontend/lib/api/user/group-tasks.ts
Normal file
13
frontend/lib/api/user/group-tasks.ts
Normal 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);
|
||||
}
|
||||
}
|
14
frontend/lib/api/user/group-webhooks.ts
Normal file
14
frontend/lib/api/user/group-webhooks.ts
Normal 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;
|
||||
}
|
78
frontend/lib/api/user/groups.ts
Normal file
78
frontend/lib/api/user/groups.ts
Normal 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);
|
||||
}
|
||||
}
|
16
frontend/lib/api/user/ocr.ts
Normal file
16
frontend/lib/api/user/ocr.ts
Normal 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 });
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/organizer-categories.ts
Normal file
20
frontend/lib/api/user/organizer-categories.ts
Normal 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));
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/organizer-tags.ts
Normal file
20
frontend/lib/api/user/organizer-tags.ts
Normal 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));
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/organizer-tools.ts
Normal file
20
frontend/lib/api/user/organizer-tools.ts
Normal 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));
|
||||
}
|
||||
}
|
48
frontend/lib/api/user/recipe-bulk-actions.ts
Normal file
48
frontend/lib/api/user/recipe-bulk-actions.ts
Normal 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);
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/recipe-foods.ts
Normal file
20
frontend/lib/api/user/recipe-foods.ts
Normal 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 });
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/recipe-units.ts
Normal file
20
frontend/lib/api/user/recipe-units.ts
Normal 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 });
|
||||
}
|
||||
}
|
1
frontend/lib/api/user/recipes/index.ts
Normal file
1
frontend/lib/api/user/recipes/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { RecipeAPI } from "./recipe";
|
19
frontend/lib/api/user/recipes/recipe-comments.ts
Normal file
19
frontend/lib/api/user/recipes/recipe-comments.ts
Normal 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));
|
||||
}
|
||||
}
|
14
frontend/lib/api/user/recipes/recipe-share.ts
Normal file
14
frontend/lib/api/user/recipes/recipe-share.ts
Normal 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;
|
||||
}
|
129
frontend/lib/api/user/recipes/recipe.ts
Normal file
129
frontend/lib/api/user/recipes/recipe.ts
Normal 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);
|
||||
}
|
||||
}
|
7
frontend/lib/api/user/upload.ts
Normal file
7
frontend/lib/api/user/upload.ts
Normal 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);
|
||||
}
|
||||
}
|
16
frontend/lib/api/user/user-registration.ts
Normal file
16
frontend/lib/api/user/user-registration.ts
Normal 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);
|
||||
}
|
||||
}
|
68
frontend/lib/api/user/users.ts
Normal file
68
frontend/lib/api/user/users.ts
Normal 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);
|
||||
}
|
||||
}
|
20
frontend/lib/api/user/utils.ts
Normal file
20
frontend/lib/api/user/utils.ts
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue