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

feat: server side search (#2112) (#2117)

* feat: server side search API (#2112)

* refactor repository_recipes filter building

* add food filter to recipe repository page_all

* fix query type annotations

* working search

* add tests and make sure title matches are ordered correctly

* remove instruction matching again

* fix formatting and small issues

* fix another linting error

* make search test no rely on actual words

* fix failing postgres compiled query

* revise incorrectly ordered migration

* automatically extract latest migration version

* test migration orderes

* run type generators

* new search function

* wip: new search page

* sortable field options

* fix virtual scroll issue

* fix search casing bug

* finalize search filters/sorts

* remove old composable

* fix type errors

---------

Co-authored-by: Sören <fleshgolem@gmx.net>
This commit is contained in:
Hayden 2023-02-11 21:26:10 -09:00 committed by GitHub
parent fc105dcebc
commit 71f8c1066a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1057 additions and 822 deletions

View file

@ -1,6 +1,6 @@
const parts = {
host: "http://localhost.com",
prefix: "/api",
prefix: "",
};
export function overrideParts(host: string, prefix: string) {

View file

@ -4,21 +4,21 @@ import { route } from ".";
describe("UrlBuilder", () => {
it("basic query parameter", () => {
const result = route("/test", { a: "b" });
expect(result).toBe("/api/test?a=b");
expect(result).toBe("/test?a=b");
});
it("multiple query parameters", () => {
const result = route("/test", { a: "b", c: "d" });
expect(result).toBe("/api/test?a=b&c=d");
expect(result).toBe("/test?a=b&c=d");
});
it("no query parameters", () => {
const result = route("/test");
expect(result).toBe("/api/test");
expect(result).toBe("/test");
});
it("list-like query parameters", () => {
const result = route("/test", { a: ["b", "c"] });
expect(result).toBe("/api/test?a=b&a=c");
expect(result).toBe("/test?a=b&a=c");
});
});

View file

@ -98,7 +98,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -121,65 +120,6 @@ export interface RecipeTool {
slug: string;
onHand?: boolean;
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
disableAmount?: boolean;
quantity?: number;
originalText?: string;
referenceId?: string;
}
export interface IngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
id: string;
createdAt?: string;
updateAt?: string;
}
export interface CreateIngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
}
export interface IngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
}
export interface CustomPageImport {
name: string;
status: boolean;

View file

@ -83,7 +83,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -100,65 +99,6 @@ export interface RecipeTag {
name: string;
slug: string;
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
disableAmount?: boolean;
quantity?: number;
originalText?: string;
referenceId?: string;
}
export interface IngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
id: string;
createdAt?: string;
updateAt?: string;
}
export interface CreateIngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
}
export interface IngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
}
export interface SaveCookBook {
name: string;
description?: string;

View file

@ -436,7 +436,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -459,34 +458,6 @@ export interface RecipeTool {
slug: string;
onHand?: boolean;
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
disableAmount?: boolean;
quantity?: number;
originalText?: string;
referenceId?: string;
}
export interface CreateIngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
}
export interface CreateIngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
}
export interface ShoppingListRemoveRecipeParams {
recipeDecrementQuantity?: number;
}

View file

@ -93,6 +93,7 @@ export interface ReadPlanEntry {
recipeId?: string;
id: number;
groupId: string;
userId?: string;
recipe?: RecipeSummary;
}
export interface RecipeSummary {
@ -113,7 +114,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -136,65 +136,6 @@ export interface RecipeTool {
slug: string;
onHand?: boolean;
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
disableAmount?: boolean;
quantity?: number;
originalText?: string;
referenceId?: string;
}
export interface IngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
id: string;
createdAt?: string;
updateAt?: string;
}
export interface CreateIngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
}
export interface IngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
}
export interface SavePlanEntry {
date: string;
entryType?: PlanEntryType & string;
@ -202,6 +143,7 @@ export interface SavePlanEntry {
text?: string;
recipeId?: string;
groupId: string;
userId?: string;
}
export interface ShoppingListIn {
name: string;
@ -222,4 +164,5 @@ export interface UpdatePlanEntry {
recipeId?: string;
id: number;
groupId: string;
userId?: string;
}

View file

@ -7,7 +7,6 @@
export type ExportTypes = "json";
export type RegisteredParser = "nlp" | "brute";
export type OrderDirection = "asc" | "desc";
export type TimelineEventType = "system" | "info" | "comment";
export interface AssignCategories {
@ -206,12 +205,12 @@ export interface Recipe {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
updateAt?: string;
lastMade?: string;
recipeIngredient?: RecipeIngredient[];
recipeInstructions?: RecipeStep[];
nutrition?: Nutrition;
settings?: RecipeSettings;
@ -282,7 +281,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -305,14 +303,6 @@ export interface RecipeCommentUpdate {
export interface RecipeDuplicate {
name?: string;
}
export interface RecipePaginationQuery {
page?: number;
perPage?: number;
orderBy?: string;
orderDirection?: OrderDirection & string;
queryFilter?: string;
loadFood?: boolean;
}
export interface RecipeShareToken {
recipeId: string;
expiresAt?: string;
@ -456,10 +446,3 @@ export interface UnitFoodBase {
export interface UpdateImageResponse {
image: string;
}
export interface PaginationQuery {
page?: number;
perPage?: number;
orderBy?: string;
orderDirection?: OrderDirection & string;
queryFilter?: string;
}

View file

@ -188,7 +188,6 @@ export interface RecipeSummary {
tools?: RecipeTool[];
rating?: number;
orgURL?: string;
recipeIngredient?: RecipeIngredient[];
dateAdded?: string;
dateUpdated?: string;
createdAt?: string;
@ -211,65 +210,6 @@ export interface RecipeTool {
slug: string;
onHand?: boolean;
}
export interface RecipeIngredient {
title?: string;
note?: string;
unit?: IngredientUnit | CreateIngredientUnit;
food?: IngredientFood | CreateIngredientFood;
disableAmount?: boolean;
quantity?: number;
originalText?: string;
referenceId?: string;
}
export interface IngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
id: string;
createdAt?: string;
updateAt?: string;
}
export interface CreateIngredientUnit {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
fraction?: boolean;
abbreviation?: string;
useAbbreviation?: boolean;
}
export interface IngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
id: string;
label?: MultiPurposeLabelSummary;
createdAt?: string;
updateAt?: string;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientFood {
name: string;
description?: string;
extras?: {
[k: string]: unknown;
};
labelId?: string;
}
export interface UserIn {
username?: string;
fullName?: string;

View file

@ -1,7 +1,7 @@
import { BaseCRUDAPI } from "../../base/base-clients";
import { route } from "../../base";
import { CommentsApi } from "./recipe-comments";
import { RecipeShareApi } from "./recipe-share";
import {
Recipe,
CreateRecipe,
@ -52,6 +52,33 @@ const routes = {
recipesSlugTimelineEventId: (slug: string, id: string) => `${prefix}/recipes/${slug}/timeline/events/${id}`,
};
export type RecipeSearchQuery ={
search: string;
orderDirection? : "asc" | "desc";
groupId?: string;
queryFilter?: string;
cookbook?: string;
categories?: string[];
requireAllCategories?: boolean;
tags?: string[];
requireAllTags?: boolean;
tools?: string[];
requireAllTools?: boolean;
foods?: string[];
requireAllFoods?: boolean;
page: number;
perPage: number;
orderBy?: string;
}
export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
baseRoute: string = routes.recipesBase;
itemRoute = routes.recipesRecipeSlug;
@ -66,6 +93,10 @@ export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
this.share = new RecipeShareApi(requests);
}
async search(rsq : RecipeSearchQuery) {
return await this.requests.get<PaginationData<Recipe>>(route(routes.recipesBase, rsq));
}
async getAllByCategory(categories: string[]) {
return await this.requests.get<Recipe[]>(routes.recipesCategory, {
categories,