diff --git a/bruno/recipe-backend/getAllRecipes.bru b/bruno/recipe-backend/getCompactRecipes.bru similarity index 81% rename from bruno/recipe-backend/getAllRecipes.bru rename to bruno/recipe-backend/getCompactRecipes.bru index aaeda4c..b96cdfc 100644 --- a/bruno/recipe-backend/getAllRecipes.bru +++ b/bruno/recipe-backend/getCompactRecipes.bru @@ -1,11 +1,11 @@ meta { - name: getAllRecipes + name: getCompactRecipes type: http seq: 5 } get { - url: http://localhost:4000/recipe + url: http://localhost:4000/compact-recipe body: none auth: bearer } diff --git a/src/controllers/CompactRecipeController.ts b/src/controllers/CompactRecipeController.ts new file mode 100644 index 0000000..395f3d9 --- /dev/null +++ b/src/controllers/CompactRecipeController.ts @@ -0,0 +1,28 @@ +import { CompactRecipeDto } from "../dtos/CompactRecipeDto.js"; +import { RecipeEntity } from "../entities/RecipeEntity.js"; +import { CompactRecipeDtoEntityMapper } from "../mappers/CompactRecipeDtoEntityMapper.js"; +import { RecipeRepository } from "../repositories/RecipeRepository.js"; + +/** + * Responsible for loading recipe header data + */ +export class CompactRecipeController { + constructor( + private repository: RecipeRepository, + private mapper: CompactRecipeDtoEntityMapper + ) {} + + /** + * Load list of all recipes + * @returns List of all recipes + */ + 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)); + }); + return recipeDtos; + } +} \ No newline at end of file diff --git a/src/controllers/RecipeController.ts b/src/controllers/RecipeController.ts index 0c77d67..9735872 100644 --- a/src/controllers/RecipeController.ts +++ b/src/controllers/RecipeController.ts @@ -1,14 +1,13 @@ import { RecipeDto } from "../dtos/RecipeDto.js"; import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js"; import { RecipeRepository } from "../repositories/RecipeRepository.js"; -import { RecipeEntity } from "../entities/RecipeEntity.js"; import { ValidationError } from "../errors/httpErrors.js"; import { RecipeInstructionStepDto } from "../dtos/RecipeInstructionStepDto.js"; import { RecipeIngredientGroupDto } from "../dtos/RecipeIngredientGroupDto.js"; import { RecipeIngredientDto } from "../dtos/RecipeIngredientDto.js"; /** - * Controls all user specific actions + * Controls all recipe specific actions */ export class RecipeController { constructor( @@ -16,19 +15,6 @@ export class RecipeController { private mapper: RecipeDtoEntityMapper ) { } - /** - * Load list of all recipes - * @returns List of all recipes - */ - async getAllRecipes() { - const recipeEntities : RecipeEntity[] = await this.repository.findAll(); - let recipeDtos: RecipeDto[] = []; - recipeEntities.forEach(recipeEntity => { - recipeDtos.push(this.mapper.toDto(recipeEntity)); - }); - return recipeDtos; - } - /** * Create a new recipe * @param dto RecipeDto containing the new recipe @@ -48,7 +34,7 @@ export class RecipeController { * @param dto RecipeDTO * @returns true if the recipe is valid */ - protected isRecipeDtoValid(dto: RecipeDto): boolean { + private isRecipeDtoValid(dto: RecipeDto): boolean { return dto.title !== undefined && dto.title.length !== 0 @@ -64,7 +50,7 @@ export class RecipeController { * @param ingredientGroups Array of ingredient groups * @returns true if all ingredient groups are valid */ - protected isIngredientGroupsValid(ingredientGroups: RecipeIngredientGroupDto[] | undefined): boolean { + private isIngredientGroupsValid(ingredientGroups: RecipeIngredientGroupDto[] | undefined): boolean { return ingredientGroups !== undefined && ingredientGroups.length !== 0 && ingredientGroups.every(group => { @@ -80,7 +66,7 @@ export class RecipeController { * @param ingredients Array if ingredients * @returns true if the ingredient list is valid */ - isIngredientsValid(ingredients: RecipeIngredientDto[] | undefined): boolean { + private isIngredientsValid(ingredients: RecipeIngredientDto[] | undefined): boolean { return ingredients !== undefined && ingredients.length !== 0 && ingredients.every(ingredient => { @@ -92,7 +78,7 @@ export class RecipeController { * @param ingredient RecipeIngredientDto * @returns true if the ingredient is valid */ - isIngredientValid(ingredient: RecipeIngredientDto): boolean { + private isIngredientValid(ingredient: RecipeIngredientDto): boolean { return ingredient !== null && ingredient.sortOrder !== undefined && ingredient.name !== undefined @@ -106,7 +92,7 @@ export class RecipeController { * @param instructions Array if instruction step DTOs * @returns Boolean indicating whether the instruction steps are valid */ - protected isInstructionsValid(instructions: RecipeInstructionStepDto[] | undefined): boolean { + private isInstructionsValid(instructions: RecipeInstructionStepDto[] | undefined): boolean { return instructions !== undefined && instructions.length !== 0 && instructions.every(step => { diff --git a/src/dtos/CompactRecipeDto.ts b/src/dtos/CompactRecipeDto.ts new file mode 100644 index 0000000..db9ad38 --- /dev/null +++ b/src/dtos/CompactRecipeDto.ts @@ -0,0 +1,11 @@ + +import { AbstractDto } from "./AbstractDto.js"; +/** + * DTO describing the essential header data of a recipe + * Used to populate lists + */ + +export class CompactRecipeDto extends AbstractDto { + title!: string; + // @todo add resource and rating here once implemented! +} \ No newline at end of file diff --git a/src/endpoints/CompactRecipePoint.ts b/src/endpoints/CompactRecipePoint.ts new file mode 100644 index 0000000..ea1db47 --- /dev/null +++ b/src/endpoints/CompactRecipePoint.ts @@ -0,0 +1,27 @@ +import { Router } from "express"; +import { asyncHandler } from "../utils/asyncHandler.js"; +import { RecipeRepository } from "../repositories/RecipeRepository.js"; +import { CompactRecipeController } from "../controllers/CompactRecipeController.js"; +import { CompactRecipeDtoEntityMapper } from "../mappers/CompactRecipeDtoEntityMapper.js"; + +/** + * Handles all recipe related routes + */ +const router = Router(); + +// Inject repo + mapper here +const recipeRepository = new RecipeRepository(); +const compactRecipeMapper = new CompactRecipeDtoEntityMapper(); +const compactRecipeController = new CompactRecipeController(recipeRepository, compactRecipeMapper); +/** + * Load header data of all recipes + */ +router.get( + "/", + asyncHandler(async (req, res) => { + const response = await compactRecipeController.getAllCompactRecipes(); + res.status(201).json(response); + }) +); + +export default router; \ No newline at end of file diff --git a/src/endpoints/RecipePoint.ts b/src/endpoints/RecipePoint.ts index 754dc6c..c982851 100644 --- a/src/endpoints/RecipePoint.ts +++ b/src/endpoints/RecipePoint.ts @@ -9,7 +9,7 @@ import { RecipeIngredientGroupDtoEntityMapper } from "../mappers/RecipeIngredien import { RecipeInstructionStepDtoEntityMapper } from "../mappers/RecipeInstructionStepDtoEntityMapper.js"; /** - * Handles all user related routes + * Handles all recipe related routes */ const router = Router(); @@ -21,7 +21,6 @@ const recipeInstructionStepMapper = new RecipeInstructionStepDtoEntityMapper(); const recipeMapper = new RecipeDtoEntityMapper(recipeInstructionStepMapper, recipeIngredientGroupMapper); const recipeController = new RecipeController(recipeRepository, recipeMapper); - /** * Create new recipe */ @@ -34,15 +33,4 @@ router.post( }) ); -/** - * Load all recipes - */ -router.get( - "/", - asyncHandler(async (req, res) => { - const response = await recipeController.getAllRecipes(); - res.status(201).json(response); - }) -); - export default router; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ee87dd4..73cf6fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import dotenv from "dotenv"; import { AppDataSource } from "./data-source.js"; import authRoutes, { authBasicRoute } from "./endpoints/AuthPoint.js"; import userRoutes from "./endpoints/UserPoint.js"; +import compactRecipeRoutes from "./endpoints/CompactRecipePoint.js"; import recipeRoutes from "./endpoints/RecipePoint.js"; import { errorHandler } from "./middleware/errorHandler.js"; import { authentication } from "./middleware/authenticationMiddleware.js"; @@ -33,6 +34,7 @@ async function startServer() { app.use(authBasicRoute, authRoutes); app.use("/user", userRoutes); app.use("/recipe", recipeRoutes); + app.use("/compact-recipe", compactRecipeRoutes); // Error handling for all rest-calls // must come last! diff --git a/src/mappers/CompactRecipeDtoEntityMapper.ts b/src/mappers/CompactRecipeDtoEntityMapper.ts new file mode 100644 index 0000000..77f2268 --- /dev/null +++ b/src/mappers/CompactRecipeDtoEntityMapper.ts @@ -0,0 +1,20 @@ +import { CompactRecipeDto } from "../dtos/CompactRecipeDto.js"; +import { RecipeEntity } from "../entities/RecipeEntity.js"; +import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; + +export class CompactRecipeDtoEntityMapper extends AbstractDtoEntityMapper{ + + toDto(entity: RecipeEntity): CompactRecipeDto { + const dto = new CompactRecipeDto(); + this.mapBaseEntityToDto(entity, dto); + + dto.title = entity.title; + + return dto; + } + + toEntity(dto: CompactRecipeDto): RecipeEntity { + throw new Error("Mapping CompactRecipeDto to RecipeEntity is not allowed!"); + } + +} \ No newline at end of file