Implement compact-recipe/list-by-filter
This commit is contained in:
parent
8ae6548dec
commit
c944b5c6b7
21 changed files with 151 additions and 76 deletions
|
|
@ -1,5 +1,5 @@
|
|||
meta {
|
||||
name: AuthPoint
|
||||
name: AuthRestResource
|
||||
seq: 2
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
meta {
|
||||
name: CompactRecipePoint
|
||||
name: CompactRecipeRestResource
|
||||
seq: 4
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
meta {
|
||||
name: getCompactRecipes
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/compact-recipe?search=kuchen
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
search: kuchen
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
meta {
|
||||
name: getListByFilter
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{url}}/compact-recipe/list-by-filter
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"searchString": "kuchen",
|
||||
"tagIdList": ["7200f6e8-3cd0-439e-a395-d7ed43e29a3e"]
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
meta {
|
||||
name: RecipePoint
|
||||
name: RecipeRestResource
|
||||
seq: 3
|
||||
}
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ post {
|
|||
|
||||
body:json {
|
||||
{
|
||||
"description": "Kuchen"
|
||||
"description": "Salat"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
meta {
|
||||
name: UserPoint
|
||||
name: UserRestResource
|
||||
seq: 5
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +10,9 @@ script:pre-request {
|
|||
try{
|
||||
// An dieser Stelle muss überprüft werden, ob diese Funktion gerade aufgerufen wird, ansonsten entsteht eine Endlosschleife.
|
||||
const blocked = bru.getEnvVar("blocked");
|
||||
console.log("blocked", blocked)
|
||||
console.log("current date", new Date())
|
||||
console.log("expire date", bru.getEnvVar("tokenExpireDate"))
|
||||
if(blocked === "false" && new Date().valueOf() > Number(bru.getEnvVar("tokenExpireDate"))){
|
||||
console.log('new Session')
|
||||
|
||||
|
|
|
|||
13
src/api/dtos/CompactRecipeFilterRequest.ts
Normal file
13
src/api/dtos/CompactRecipeFilterRequest.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Request wrapper for searching recipes based on a filter
|
||||
*/
|
||||
export class CompactRecipeFilterRequest {
|
||||
/**
|
||||
* Search string applied to the recipe title
|
||||
*/
|
||||
searchString?: string;
|
||||
/**
|
||||
* List of tags that must be applied to the recipe
|
||||
*/
|
||||
tagIdList?: string[];
|
||||
}
|
||||
8
src/api/dtos/CompactRecipeFilterResponse.ts
Normal file
8
src/api/dtos/CompactRecipeFilterResponse.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import {CompactRecipeDto} from "./CompactRecipeDto.js";
|
||||
|
||||
/**
|
||||
* Filter response containing a list of all recipes matching the search
|
||||
*/
|
||||
export class CompactRecipeFilterResponse {
|
||||
compactRecipeList! : CompactRecipeDto[];
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import { RecipeRepository } from "../../repositories/RecipeRepository.js";
|
|||
import { CompactRecipeHandler } from "../../handlers/CompactRecipeHandler.js";
|
||||
import { CompactRecipeDtoEntityMapper } from "../../mappers/CompactRecipeDtoEntityMapper.js";
|
||||
import {HttpStatusCode} from "../apiHelpers/HttpStatusCodes.js";
|
||||
import {CompactRecipeFilterRequest} from "../dtos/CompactRecipeFilterRequest.js";
|
||||
import {CompactRecipeFilterResponse} from "../dtos/CompactRecipeFilterResponse.js";
|
||||
|
||||
export const compactRecipeBasicRoute = "/compact-recipe"
|
||||
/**
|
||||
|
|
@ -16,8 +18,6 @@ const compactRecipeHandler = new CompactRecipeHandler(new RecipeRepository(), ne
|
|||
/**
|
||||
* Load header data of all recipes
|
||||
* Responds with a list of CompactRecipeDtos
|
||||
* @todo request wrapper DTO
|
||||
* @todo response wrapper DTO
|
||||
*/
|
||||
router.get(
|
||||
"/",
|
||||
|
|
@ -30,4 +30,12 @@ router.get(
|
|||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/list-by-filter",
|
||||
asyncHandler(async (req , res) => {
|
||||
const request : CompactRecipeFilterRequest = req.body;
|
||||
const response : CompactRecipeFilterResponse = await compactRecipeHandler.getRecipesByFilter(request);
|
||||
res.status(HttpStatusCode.OK).json(response);
|
||||
})
|
||||
)
|
||||
export default router;
|
||||
|
|
@ -2,6 +2,8 @@ import { CompactRecipeDto } from "../api/dtos/CompactRecipeDto.js";
|
|||
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
||||
import { CompactRecipeDtoEntityMapper } from "../mappers/CompactRecipeDtoEntityMapper.js";
|
||||
import { RecipeRepository } from "../repositories/RecipeRepository.js";
|
||||
import {CompactRecipeFilterRequest} from "../api/dtos/CompactRecipeFilterRequest.js";
|
||||
import {CompactRecipeFilterResponse} from "../api/dtos/CompactRecipeFilterResponse.js";
|
||||
|
||||
/**
|
||||
* Responsible for loading recipe header data
|
||||
|
|
@ -18,7 +20,6 @@ export class CompactRecipeHandler {
|
|||
*/
|
||||
async getAllCompactRecipes() {
|
||||
const recipeEntities: RecipeEntity[] = await this.repository.findAll();
|
||||
// @todo load instruction steps, ingredient groups and ingredients before mapping!
|
||||
let recipeDtos: CompactRecipeDto[] = [];
|
||||
recipeEntities.forEach(recipeEntity => {
|
||||
recipeDtos.push(this.mapper.toDto(recipeEntity));
|
||||
|
|
@ -38,7 +39,23 @@ export class CompactRecipeHandler {
|
|||
// get all
|
||||
return this.getAllCompactRecipes();
|
||||
} else {
|
||||
return this.repository.findCompactRecipeBySearch(searchString);
|
||||
const tagIdList : string[] = [];
|
||||
return this.repository.findCompactRecipeByFilter(searchString, tagIdList);
|
||||
}
|
||||
}
|
||||
|
||||
async getRecipesByFilter(request: CompactRecipeFilterRequest) : Promise<CompactRecipeFilterResponse> {
|
||||
const searchString = request.searchString;
|
||||
const tagIdList = request.tagIdList;
|
||||
var recipeEntities : RecipeEntity[] = await this.repository.findCompactRecipeByFilter(searchString, tagIdList);
|
||||
const response = new CompactRecipeFilterResponse();
|
||||
// @todo move list mapping to mapper
|
||||
let recipeDtos: CompactRecipeDto[] = [];
|
||||
// Add mapper function to map the result list.
|
||||
recipeEntities.forEach(recipeEntity => {
|
||||
recipeDtos.push(this.mapper.toDto(recipeEntity));
|
||||
});
|
||||
response.compactRecipeList = recipeDtos;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { AbstractRepository } from "./AbstractRepository.js";
|
||||
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
||||
import { AppDataSource } from "../data-source.js";
|
||||
import { ILike } from "typeorm";
|
||||
import { UUID } from "crypto";
|
||||
|
||||
export class RecipeRepository extends AbstractRepository<RecipeEntity> {
|
||||
constructor() {
|
||||
|
|
@ -14,27 +14,51 @@ export class RecipeRepository extends AbstractRepository<RecipeEntity> {
|
|||
* @returns RecipeEntity including all relations such as ingredients groups, ingredients and instruction steps
|
||||
*/
|
||||
async findById(id: string): Promise<RecipeEntity | null> {
|
||||
return this.repo.findOne(
|
||||
{ where: { id } as any,
|
||||
return this.repo.findOne({
|
||||
where: { id } as any,
|
||||
relations: [
|
||||
'ingredientGroups',
|
||||
'ingredientGroups.ingredients',
|
||||
'instructionSteps',
|
||||
'tagList',
|
||||
]
|
||||
"ingredientGroups",
|
||||
"ingredientGroups.ingredients",
|
||||
"instructionSteps",
|
||||
"tagList",
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all recipes matching the search. Currently it only searches on the title. Fetches only recipe header data but no relations.
|
||||
* @param searchString String to search for
|
||||
* @returns List of recipe entities matching the search criteria
|
||||
* Find all recipes matching the search. Currently, it only searches on the title. Fetches only recipe header data but no relations.
|
||||
* @param searchString String to search for. Might be undefined if no search is applied (all recipes or filter for tags only)
|
||||
* @param tagIdList List of tags. The recipe must have all the tags to be taken into account. May be undefined if not filtering for tags
|
||||
* @returns List of recipe entities matching the filter criteria
|
||||
*/
|
||||
async findCompactRecipeBySearch(searchString : string): Promise<RecipeEntity[] | null>{
|
||||
// @todo doesn't work like expected...
|
||||
return this.repo.find(
|
||||
{ where: {title: ILike(`%${searchString}%`)}}
|
||||
async findCompactRecipeByFilter(
|
||||
searchString: string | undefined,
|
||||
tagIdList: string[] | UUID[] | undefined
|
||||
): Promise<RecipeEntity[]> {
|
||||
const qb = this.repo.createQueryBuilder("recipe");
|
||||
|
||||
// Filter by title if a search string is provided
|
||||
if (searchString !== undefined && searchString.length > 0) {
|
||||
qb.andWhere("recipe.title ILIKE :title", { title: `%${searchString}%` });
|
||||
}
|
||||
|
||||
// Filter by tags: the recipe must have ALL tags in the list
|
||||
if (tagIdList !== undefined && tagIdList.length > 0) {
|
||||
// Join recipe_tag for each required tag individually so that only
|
||||
// recipes possessing every tag are returned (AND semantics, not OR)
|
||||
tagIdList.forEach((tagId, index) => {
|
||||
const alias = `tag_${index}`;
|
||||
const param = `tagId_${index}`;
|
||||
qb.innerJoin(
|
||||
"recipe.tagList",
|
||||
alias,
|
||||
`${alias}.id = :${param}`,
|
||||
{ [param]: tagId }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,6 +78,5 @@ export class RecipeRepository extends AbstractRepository<RecipeEntity> {
|
|||
this.repo.merge(existing, entity);
|
||||
return this.repo.save(existing);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue