From e33dfdb84588a0fd6745b0d5b035de44bd9c6bf7 Mon Sep 17 00:00:00 2001 From: Anika Raemer Date: Sun, 28 Sep 2025 20:28:53 +0200 Subject: [PATCH] implement update recipe - does not create new ingredient groups, incredients or instruction steps yet --- bruno/recipe-backend/updateRecipe.bru | 117 ++++++++++++++++++++++++++ src/controllers/RecipeController.ts | 20 +++++ src/endpoints/RecipePoint.ts | 16 +++- 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 bruno/recipe-backend/updateRecipe.bru diff --git a/bruno/recipe-backend/updateRecipe.bru b/bruno/recipe-backend/updateRecipe.bru new file mode 100644 index 0000000..85a4191 --- /dev/null +++ b/bruno/recipe-backend/updateRecipe.bru @@ -0,0 +1,117 @@ +meta { + name: updateRecipe + type: http + seq: 7 +} + +put { + url: http://localhost:4000/recipe/44a8f38c-9387-439e-aed6-c3369b776b1c + body: json + auth: bearer +} + +auth:bearer { + token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImE0NDdlNDM0LTQyMWYtNDJiYS04MGRlLTM0ZDE1YzJmNWE2YyIsImlhdCI6MTc1OTA3NjA2NCwiZXhwIjoxNzU5MTYyNDY0fQ.exf_3fCrarW0LhUqPuadvp89BOUazEXtdSTkGDIAU_Q +} + +body:json { + { + "id": "44a8f38c-9387-439e-aed6-c3369b776b1c", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "title": "Apfelkuchen Edeltrud", + "amount": 1, + "amountDescription": "Kuchen (26cm Durchmesser)", + "instructions": [ + { + "id": "9042d658-0102-4e63-8637-a82c5653aa9d", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "text": "Mürbteig von 400 g Mehl herstellen", + "sortOrder": 1 + }, + { + "id": "42f834f1-54d1-4149-ad2e-e6565add719b", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "text": "Äpfel schälen und kleinschneiden.", + "sortOrder": 2 + }, + { + "id": "a45ad765-f775-4969-ad36-ca2d5645a2fc", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "text": "Restlichen Teig ausrollen, Kuchen damit abdecken und mit Milch bepinseln.", + "sortOrder": 4 + }, + { + "id": "a45ad765-f775-4969-ad36-ca2d5645a2fc", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "text": "Springform fetten, zwei Drittel des Teigs hineindrücken, Äpfel mit Rosinen vermischen und einfüllen.", + "sortOrder": 3 + }, + { + "id": "e0435853-b1b9-46cb-b53f-f5345ffca729", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "text": "Backen", + "sortOrder": 5 + } + ], + "ingredientGroups": [ + { + "id": "680b885a-6670-4982-b257-336a0b4c09ee", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "title": "Für den Teig", + "sortOrder": 1, + "ingredients": [ + { + "id": "d216f7f5-3e16-46f2-914f-af012ded9623", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "amount": 400, + "name": "Mehl", + "unit": "g", + "sortOrder": 1, + "subtext": null + } + ] + }, + { + "id": "ae6d32f4-a383-436b-8b9e-f693d74c955e", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "title": "Für die Füllung", + "sortOrder": 2, + "ingredients": [ + { + "id": "9221faa8-a923-4c97-bb92-6d2dd7e94afc", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "amount": 5, + "name": "große Äpfel", + "unit": null, + "sortOrder": 1, + "subtext": null + }, + { + "id": "7f3264d3-a002-40a7-92e2-4b3d02ea8bfd", + "createdAt": "2025-09-28T10:24:05.429Z", + "updatedAt": "2025-09-28T10:24:05.429Z", + "amount": 100, + "name": "Rosinen", + "unit": "g", + "sortOrder": 2, + "subtext": null + } + ] + } + ] + } +} + +settings { + encodeUrl: true +} diff --git a/src/controllers/RecipeController.ts b/src/controllers/RecipeController.ts index 8e99b1d..44b3b37 100644 --- a/src/controllers/RecipeController.ts +++ b/src/controllers/RecipeController.ts @@ -5,6 +5,7 @@ import { NotFoundError, ValidationError } from "../errors/httpErrors.js"; import { RecipeInstructionStepDto } from "../dtos/RecipeInstructionStepDto.js"; import { RecipeIngredientGroupDto } from "../dtos/RecipeIngredientGroupDto.js"; import { RecipeIngredientDto } from "../dtos/RecipeIngredientDto.js"; +import { Entity } from "typeorm"; /** * Controls all recipe specific actions @@ -15,6 +16,11 @@ export class RecipeController { private mapper: RecipeDtoEntityMapper ) { } + /** + * Load a specific recipe + * @param id recipe id + * @returns RecipeDto for requested recipe + */ async getRecipeById(id: string){ const recipeEntity = await this.recipeRepository.findById(id); if(recipeEntity === null){ @@ -23,6 +29,20 @@ export class RecipeController { const recipeDto = this.mapper.toDto(recipeEntity); return recipeDto; } + /** + * Update recipe data + * @param RecipeDto containing the entire updated recipe + * @returns Up-to-date RecipeDto as saved in the database + */ + async updateRecipe(dto: RecipeDto){ + if (!this.isRecipeDtoValid(dto)) { + throw new ValidationError("recipe data is not valid!") + } + const recipeEntity = this.mapper.toEntity(dto); + // @todo doesn't create new ingredient groups, ingredients or instruction steps yet + const savedEntity = await this.recipeRepository.save(recipeEntity); + return this.mapper.toDto(savedEntity); + } /** * Create a new recipe * @param dto RecipeDto containing the new recipe diff --git a/src/endpoints/RecipePoint.ts b/src/endpoints/RecipePoint.ts index d2b7825..dde7a17 100644 --- a/src/endpoints/RecipePoint.ts +++ b/src/endpoints/RecipePoint.ts @@ -7,6 +7,7 @@ import { RecipeDto } from "../dtos/RecipeDto.js"; import { RecipeIngredientDtoEntityMapper } from "../mappers/RecipeIngredientDtoEntityMapper.js"; import { RecipeIngredientGroupDtoEntityMapper } from "../mappers/RecipeIngredientGroupDtoEntityMapper.js"; import { RecipeInstructionStepDtoEntityMapper } from "../mappers/RecipeInstructionStepDtoEntityMapper.js"; +import { ValidationError } from "../errors/httpErrors.js"; /** * Handles all recipe related routes @@ -40,6 +41,19 @@ router.get( const responseDto = await recipeController.getRecipeById(id); res.status(201).json(responseDto); }) -) +); + +router.put( + "/:id", + asyncHandler(async(req, res) =>{ + const id = req.params.id; + const recipeDto : RecipeDto = req.body; + if(id != recipeDto.id){ + throw new ValidationError("Cannot save recipe! ID in request body " + recipeDto.id + " doesn't match ID in path " + id +"!") + } + const responseDto = await recipeController.updateRecipe(recipeDto); + res.status(201).json(responseDto); + }) +); export default router; \ No newline at end of file