diff --git a/bruno/recipe-backend/RecipeRestResourcePoint/getRecipeById.bru b/bruno/recipe-backend/RecipeRestResourcePoint/getRecipeById.bru index 4757cac..66a580a 100644 --- a/bruno/recipe-backend/RecipeRestResourcePoint/getRecipeById.bru +++ b/bruno/recipe-backend/RecipeRestResourcePoint/getRecipeById.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{url}}/recipe/fa608340-d679-4267-8b89-c743bd7fc234 + url: {{url}}/recipe/44a8f38c-9387-439e-aed6-c3369b776b1c body: none auth: inherit } diff --git a/bruno/recipe-backend/TagRestResource/createOrUpdate.bru b/bruno/recipe-backend/TagRestResource/createOrUpdate.bru new file mode 100644 index 0000000..d04999f --- /dev/null +++ b/bruno/recipe-backend/TagRestResource/createOrUpdate.bru @@ -0,0 +1,21 @@ +meta { + name: createOrUpdate + type: http + seq: 2 +} + +post { + url: {{url}}/tag/create-or-update + body: json + auth: inherit +} + +body:json { + { + "description": "Kuchen" + } +} + +settings { + encodeUrl: true +} diff --git a/bruno/recipe-backend/TagRestResource/delete.bru b/bruno/recipe-backend/TagRestResource/delete.bru new file mode 100644 index 0000000..2528dac --- /dev/null +++ b/bruno/recipe-backend/TagRestResource/delete.bru @@ -0,0 +1,15 @@ +meta { + name: delete + type: http + seq: 3 +} + +delete { + url: {{url}}/tag/374d5df2-f7ff-45cd-ac67-c213233bf83f + body: none + auth: inherit +} + +settings { + encodeUrl: true +} diff --git a/bruno/recipe-backend/TagRestResource/folder.bru b/bruno/recipe-backend/TagRestResource/folder.bru new file mode 100644 index 0000000..4eb956f --- /dev/null +++ b/bruno/recipe-backend/TagRestResource/folder.bru @@ -0,0 +1,8 @@ +meta { + name: TagRestResource + seq: 5 +} + +auth { + mode: inherit +} diff --git a/bruno/recipe-backend/TagRestResource/getAll.bru b/bruno/recipe-backend/TagRestResource/getAll.bru new file mode 100644 index 0000000..3d8b104 --- /dev/null +++ b/bruno/recipe-backend/TagRestResource/getAll.bru @@ -0,0 +1,15 @@ +meta { + name: getAll + type: http + seq: 1 +} + +get { + url: {{url}}/tag/all + body: none + auth: inherit +} + +settings { + encodeUrl: true +} diff --git a/src/api/endpoints/TagRestResource.ts b/src/api/endpoints/TagRestResource.ts index ddc622a..9d6424f 100644 --- a/src/api/endpoints/TagRestResource.ts +++ b/src/api/endpoints/TagRestResource.ts @@ -8,9 +8,9 @@ import {requireAdmin} from "../../middleware/authorizationMiddleware.js"; * REST resource for tags. * * Routes: - * GET /tags — list all tags (authenticated users) - * POST /tags/create-or-update — create or update a tag (authenticated users) - * DELETE /tags/:id — delete a tag (administrators only) + * GET /tag — list all tags (authenticated users) + * POST /tag/create-or-update — create or update a tag (authenticated users) + * DELETE /tag/:id — delete a tag (administrators only) * * Authentication / authorisation is assumed to be enforced by middleware * applied upstream (e.g. a global JWT guard). The adminOnly middleware below @@ -22,7 +22,7 @@ const tagHandler = new TagHandler(); /** - * GET /tags + * GET /tag/all * Returns all available tags. */ router.get("/all", async (_req: Request, res: Response, next: NextFunction) => { @@ -35,9 +35,9 @@ router.get("/all", async (_req: Request, res: Response, next: NextFunction) => { }); /** - * POST /tags/create-or-update + * POST /tag/create-or-update * Creates a new tag or updates an existing one. - * If a tag with the given descriprion already exists in the database, that tag is returned instead of creating + * If a tag with the given description already exists in the database, that tag is returned instead of creating * a duplicate. * Body: TagDto */ @@ -55,7 +55,7 @@ router.post( ); /** - * DELETE /tags/:id + * DELETE /tag/:id * Deletes a tag by ID. Restricted to administrators. * The database cascade removes all entries in the recipe_tag mapping table. */ diff --git a/src/mappers/RecipeDtoEntityMapper.ts b/src/mappers/RecipeDtoEntityMapper.ts index fe4240a..c582322 100644 --- a/src/mappers/RecipeDtoEntityMapper.ts +++ b/src/mappers/RecipeDtoEntityMapper.ts @@ -6,6 +6,7 @@ import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; import { RecipeIngredientGroupDtoEntityMapper } from "./RecipeIngredientGroupDtoEntityMapper.js"; import { RecipeInstructionStepDtoEntityMapper } from "./RecipeInstructionStepDtoEntityMapper.js"; import { TagDtoEntityMapper } from "./TagDtoEntityMapper.js"; +import {TagEntity} from "../entities/TagEntity.js"; export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper { constructor( @@ -114,12 +115,15 @@ export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper - this.tagMapper.toEntity(tagDto) - ); + /* Only build a stub here instead of using the mapper to avoid causing a cascade on the tag table while + * updating the join table correctly. For this purpose, the ORM only needs to know the tag ID in order to + * signal that we are dealing with an existing tag. + */ + entity.tagList = (dto.tagList ?? []).map((tagDto) => { + const stub = new TagEntity(); + stub.id = tagDto.id; + return stub; + }); return entity; } diff --git a/src/repositories/RecipeRepository.ts b/src/repositories/RecipeRepository.ts index 8c62cb2..f4226c1 100644 --- a/src/repositories/RecipeRepository.ts +++ b/src/repositories/RecipeRepository.ts @@ -1,7 +1,7 @@ import { AbstractRepository } from "./AbstractRepository.js"; import { RecipeEntity } from "../entities/RecipeEntity.js"; import { AppDataSource } from "../data-source.js"; -import { ILike, Like } from "typeorm"; +import { ILike } from "typeorm"; export class RecipeRepository extends AbstractRepository { constructor() { @@ -19,7 +19,8 @@ export class RecipeRepository extends AbstractRepository { relations: [ 'ingredientGroups', 'ingredientGroups.ingredients', - 'instructionSteps' + 'instructionSteps', + 'tagList', ] }); } @@ -46,7 +47,7 @@ export class RecipeRepository extends AbstractRepository { // load existing data const existing = await this.repo.findOneOrFail({ where: { id: entity.id }, - relations: ["instructionSteps", "ingredientGroups", "ingredientGroups.ingredients"], + relations: ["instructionSteps", "ingredientGroups", "ingredientGroups.ingredients", "tagList"], }); // merge new entity and existing entity