diff --git a/bruno/recipe-backend/createRecipe.bru b/bruno/recipe-backend/createRecipe.bru new file mode 100644 index 0000000..d44d720 --- /dev/null +++ b/bruno/recipe-backend/createRecipe.bru @@ -0,0 +1,62 @@ +meta { + name: createRecipe + type: http + seq: 4 +} + +post { + url: http://localhost:4000/recipe + body: json + auth: bearer +} + +auth:bearer { + token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImE0NDdlNDM0LTQyMWYtNDJiYS04MGRlLTM0ZDE1YzJmNWE2YyIsImlhdCI6MTc1ODk4Njk4MSwiZXhwIjoxNzU5MDczMzgxfQ.rYvECzhI3Tptse3yVjZvR9RXgs1gkwAt2_5-hpAXvB0 +} + +body:json { + { + "title": "Spaghetti mit Tomatensosse", + "amount": "4", + "amountDescription": "Personen", + "instructions": [{ + "text": "Spaghetti nach Packungsanleitung zubereiten", + "sortOrder": 1 + }, + { + "text": "Tomatensosse erhitzen", + "sortOrder": 2 + }, + { + "text": "Vermischen, mit geriebenem Parmesan bestreuen und servieren", + "sortOrder": 3 + }], + "ingredientGroups": [ + { + "sortOrder": 1, + "ingredients":[ + { + "name": "Spaghetti", + "amount": 500, + "unit": "g", + "sortOrder": 1 + }, + { + "name": "Tomatensosse", + "amount": 1, + "unit": "Glas", + "sortOrder": 2 + }, + { + "name": "Parmesan", + "sortOrder": 3 + } + ] + } + ] + } +} + +settings { + encodeUrl: true +} diff --git a/bruno/recipe-backend/getAllRecipes.bru b/bruno/recipe-backend/getAllRecipes.bru new file mode 100644 index 0000000..aaeda4c --- /dev/null +++ b/bruno/recipe-backend/getAllRecipes.bru @@ -0,0 +1,19 @@ +meta { + name: getAllRecipes + type: http + seq: 5 +} + +get { + url: http://localhost:4000/recipe + body: none + auth: bearer +} + +auth:bearer { + token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImE0NDdlNDM0LTQyMWYtNDJiYS04MGRlLTM0ZDE1YzJmNWE2YyIsImlhdCI6MTc1ODk4Njk4MSwiZXhwIjoxNzU5MDczMzgxfQ.rYvECzhI3Tptse3yVjZvR9RXgs1gkwAt2_5-hpAXvB0 +} + +settings { + encodeUrl: true +} diff --git a/src/controllers/RecipeController.ts b/src/controllers/RecipeController.ts index 03a88d1..0c77d67 100644 --- a/src/controllers/RecipeController.ts +++ b/src/controllers/RecipeController.ts @@ -1,76 +1,129 @@ -import { Entity } from "typeorm"; 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 */ export class RecipeController { - constructor( - private repository: RecipeRepository, - private mapper: RecipeDtoEntityMapper - ) {} + constructor( + private repository: RecipeRepository, + private mapper: RecipeDtoEntityMapper + ) { } - /** - * Load list of all recipes - * @returns List of all recipes - */ - async getAllRecipes(){ - const recipeEntities = 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 - */ - async createRecipe(dto : RecipeDto){ - if(!this.isRecipeDtoValid(dto)){ - throw new ValidationError("recipe data is not valid!") + /** + * 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; } - const recipeEntity = this.mapper.toEntity(dto) - const entity = await this.repository.create(recipeEntity); - return this.mapper.toDto(entity); - } - protected isRecipeDtoValid(dto :RecipeDto) : boolean { - let isValid = true; - if(dto.title == undefined - || dto.title.length == 0 - || dto.amount == undefined - || dto.amountDescription == undefined - || dto.amountDescription.length == 0 - || !this.isInstructionsValid(dto.instructions) - ){ - isValid = false; - } - - // @todo ingredient groups - - return isValid; - } - protected isInstructionsValid(instructions: RecipeInstructionStepDto[]|undefined) : boolean { - if(instructions == undefined || instructions.length == 0){ - return false; - } - let isValid = true; - instructions.forEach(step =>{ - if(step.text == undefined - || step.text.length == 0 - || step.sortOrder == undefined - ){ - isValid = false; + /** + * Create a new recipe + * @param dto RecipeDto containing the new recipe + */ + async createRecipe(dto: RecipeDto) { + if (!this.isRecipeDtoValid(dto)) { + throw new ValidationError("recipe data is not valid!") } - }); - - return isValid; - } + const recipeEntity = this.mapper.toEntity(dto) + const entity = await this.repository.create(recipeEntity); + return this.mapper.toDto(entity); + } + + /** + * Validates a recipe. + * A recipe must have a non-empty title, servings info and valid instructions and ingredients + * @param dto RecipeDTO + * @returns true if the recipe is valid + */ + protected isRecipeDtoValid(dto: RecipeDto): boolean { + + return dto.title !== undefined + && dto.title.length !== 0 + && dto.amount !== undefined + && dto.amountDescription !== undefined + && dto.amountDescription.length !== 0 + && this.isInstructionsValid(dto.instructions) + && this.isIngredientGroupsValid(dto.ingredientGroups); + } + + /** + * Validates the ingredient groups if a recipe - each group must contain a valid ingredient list + * @param ingredientGroups Array of ingredient groups + * @returns true if all ingredient groups are valid + */ + protected isIngredientGroupsValid(ingredientGroups: RecipeIngredientGroupDto[] | undefined): boolean { + return ingredientGroups !== undefined + && ingredientGroups.length !== 0 + && ingredientGroups.every(group => { + return ( + group.sortOrder !== undefined + && this.isIngredientsValid(group.ingredients) + ); + }); + } + /** + * Validates an ingredient list ensuring that it is present, contains at least one ingredient and + * that all ingredients in the list are valid + * @param ingredients Array if ingredients + * @returns true if the ingredient list is valid + */ + isIngredientsValid(ingredients: RecipeIngredientDto[] | undefined): boolean { + return ingredients !== undefined + && ingredients.length !== 0 + && ingredients.every(ingredient => { + return this.isIngredientValid(ingredient); + }); + } + /** + * Validates an ingredient - An ingredient must have a name and a sortOrder + * @param ingredient RecipeIngredientDto + * @returns true if the ingredient is valid + */ + isIngredientValid(ingredient: RecipeIngredientDto): boolean { + return ingredient !== null + && ingredient.sortOrder !== undefined + && ingredient.name !== undefined + && ingredient.name.length !== 0 + } + // @todo create instruction handler/controller for validation? + /** + * Validates instructions + * The list must be present and must contain at least one value with a non-empty + * text field and sort order + * @param instructions Array if instruction step DTOs + * @returns Boolean indicating whether the instruction steps are valid + */ + protected isInstructionsValid(instructions: RecipeInstructionStepDto[] | undefined): boolean { + return instructions !== undefined + && instructions.length !== 0 + && instructions.every(step => { + return this.isInstructionStepValid(step); + }); + } + + /** + * Validates a single instruction step. Each step must have a well-defined non-empty text + * and a sort order + * @param step InstructionStepDto describing a single step of the recipe + * @returns true if the step is valid + */ + private isInstructionStepValid(step: RecipeInstructionStepDto): boolean { + return step !== null + && step.text !== undefined + && step.text.length !== 0 + && step.sortOrder !== undefined; + } } \ No newline at end of file diff --git a/src/dtos/RecipeDto.ts b/src/dtos/RecipeDto.ts index beb3be6..b8d757f 100644 --- a/src/dtos/RecipeDto.ts +++ b/src/dtos/RecipeDto.ts @@ -10,6 +10,6 @@ export class RecipeDto extends AbstractDto { title!: string; amount?: number amountDescription?: string; - instructions?: RecipeInstructionStepDto[]; - ingredientGroups?: RecipeIngredientGroupDto[]; + instructions!: RecipeInstructionStepDto[]; + ingredientGroups!: RecipeIngredientGroupDto[]; } \ No newline at end of file diff --git a/src/dtos/RecipeIngredientDto.ts b/src/dtos/RecipeIngredientDto.ts index a50b161..20cfa9f 100644 --- a/src/dtos/RecipeIngredientDto.ts +++ b/src/dtos/RecipeIngredientDto.ts @@ -2,10 +2,10 @@ import { UUID } from "crypto"; import { AbstractDto } from "./AbstractDto.js"; export class RecipeIngredientDto extends AbstractDto{ - name?: string; + name!: string; subtext?: string; amount?: number; unit?: string; - sortOrder?: number; + sortOrder!: number; ingredientGroupId?: UUID; } \ No newline at end of file diff --git a/src/dtos/RecipeIngredientGroupDto.ts b/src/dtos/RecipeIngredientGroupDto.ts index 36342f4..32d6cc0 100644 --- a/src/dtos/RecipeIngredientGroupDto.ts +++ b/src/dtos/RecipeIngredientGroupDto.ts @@ -4,7 +4,7 @@ import { RecipeIngredientDto } from "./RecipeIngredientDto.js"; export class RecipeIngredientGroupDto extends AbstractDto{ title?: string; - sortOrder?: string; + sortOrder!: number; recipeId?: UUID; - ingredients?: RecipeIngredientDto[]; + ingredients!: RecipeIngredientDto[]; } \ No newline at end of file diff --git a/src/dtos/RecipeInstructionStepDto.ts b/src/dtos/RecipeInstructionStepDto.ts index c4bd86c..362e282 100644 --- a/src/dtos/RecipeInstructionStepDto.ts +++ b/src/dtos/RecipeInstructionStepDto.ts @@ -2,7 +2,7 @@ import { UUID } from "crypto"; import { AbstractDto } from "./AbstractDto.js"; export class RecipeInstructionStepDto extends AbstractDto{ - text?: string; - sortOrder?: number; + text!: string; + sortOrder!: number; recipeId?: UUID; } \ No newline at end of file diff --git a/src/endpoints/RecipePoint.ts b/src/endpoints/RecipePoint.ts index 8dc088a..754dc6c 100644 --- a/src/endpoints/RecipePoint.ts +++ b/src/endpoints/RecipePoint.ts @@ -3,6 +3,10 @@ import { RecipeRepository } from "../repositories/RecipeRepository.js"; import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js"; import { RecipeController } from "../controllers/RecipeController.js"; import { asyncHandler } from "../utils/asyncHandler.js"; +import { RecipeDto } from "../dtos/RecipeDto.js"; +import { RecipeIngredientDtoEntityMapper } from "../mappers/RecipeIngredientDtoEntityMapper.js"; +import { RecipeIngredientGroupDtoEntityMapper } from "../mappers/RecipeIngredientGroupDtoEntityMapper.js"; +import { RecipeInstructionStepDtoEntityMapper } from "../mappers/RecipeInstructionStepDtoEntityMapper.js"; /** * Handles all user related routes @@ -11,16 +15,34 @@ const router = Router(); // Inject repo + mapper here const recipeRepository = new RecipeRepository(); -const recipeDtoEntityMapper = new RecipeDtoEntityMapper(); -const recipeController = new RecipeController(recipeRepository, recipeDtoEntityMapper); +const recipeIngredientMapper = new RecipeIngredientDtoEntityMapper(); +const recipeIngredientGroupMapper = new RecipeIngredientGroupDtoEntityMapper(recipeIngredientMapper); +const recipeInstructionStepMapper = new RecipeInstructionStepDtoEntityMapper(); +const recipeMapper = new RecipeDtoEntityMapper(recipeInstructionStepMapper, recipeIngredientGroupMapper); +const recipeController = new RecipeController(recipeRepository, recipeMapper); + /** - * Create a new user + * Create new recipe + */ +router.post( + "/", + asyncHandler(async (req, res) => { + const requestDto : RecipeDto = req.body; + const responseDto = await recipeController.createRecipe(requestDto); + res.status(201).json(responseDto); + }) +); + +/** + * Load all recipes */ router.get( "/", - asyncHandler(async (req, res) => { + asyncHandler(async (req, res) => { const response = await recipeController.getAllRecipes(); res.status(201).json(response); }) -); \ No newline at end of file +); + +export default router; \ No newline at end of file diff --git a/src/entities/RecipeIngredientEntity.ts b/src/entities/RecipeIngredientEntity.ts index 93639b4..eb90523 100644 --- a/src/entities/RecipeIngredientEntity.ts +++ b/src/entities/RecipeIngredientEntity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, ManyToOne, Relation} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, Relation} from "typeorm"; import { AbstractEntity } from "./AbstractEntity.js"; import { RecipeIngredientGroupEntity } from "./RecipeIngredientGroupEntity.js"; @@ -19,11 +19,12 @@ export class RecipeIngredientEntity extends AbstractEntity { @Column({nullable: true}) unit?: string; - @Column({ nullable: false }) + @Column({ name: "sort_order", nullable: false }) sortOrder!: number; + @JoinColumn({name: "recipe_ingredient_group_id"}) @ManyToOne(() => RecipeIngredientGroupEntity, (ingredientGroup) => ingredientGroup.ingredients, - {onDelete: "CASCADE"} + {onDelete: "CASCADE", nullable: false} ) ingredientGroup!: Relation; } \ No newline at end of file diff --git a/src/entities/RecipeIngredientGroupEntity.ts b/src/entities/RecipeIngredientGroupEntity.ts index 1a7119a..64c64c3 100644 --- a/src/entities/RecipeIngredientGroupEntity.ts +++ b/src/entities/RecipeIngredientGroupEntity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, ManyToOne, OneToMany, Relation} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, Relation} from "typeorm"; import { AbstractEntity } from "./AbstractEntity.js"; import { RecipeEntity } from "./RecipeEntity.js"; import { RecipeIngredientEntity } from "./RecipeIngredientEntity.js"; @@ -11,11 +11,12 @@ export class RecipeIngredientGroupEntity extends AbstractEntity { @Column({ nullable: true }) title?: string; - @Column({ nullable: false }) + @Column({ name: "sort_order", nullable: false }) sortOrder!: number; + @JoinColumn({name: "recipe_id"}) @ManyToOne(() => RecipeEntity, (recipe) => recipe.ingredientGroups, - {onDelete: "CASCADE"} + {onDelete: "CASCADE", nullable: false} ) recipe!: Relation; diff --git a/src/entities/RecipeInstructionStepEntity.ts b/src/entities/RecipeInstructionStepEntity.ts index d4cb539..ed238d1 100644 --- a/src/entities/RecipeInstructionStepEntity.ts +++ b/src/entities/RecipeInstructionStepEntity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, ManyToOne, Relation} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, Relation} from "typeorm"; import { AbstractEntity } from "./AbstractEntity.js"; import { RecipeEntity } from "./RecipeEntity.js"; @@ -10,11 +10,12 @@ export class RecipeInstructionStepEntity extends AbstractEntity { @Column({ nullable: false }) text!: string; - @Column({ nullable: false }) + @Column({ name: "sort_order", nullable: false }) sortOrder!: number; + @JoinColumn({name: "recipe_id"}) @ManyToOne(() => RecipeEntity, (recipe) => recipe.instructionSteps, - {onDelete: "CASCADE"} + {onDelete: "CASCADE", nullable: false} ) recipe!: Relation; diff --git a/src/index.ts b/src/index.ts index 3bd3c18..ee87dd4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +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 recipeRoutes from "./endpoints/RecipePoint.js"; +import recipeRoutes from "./endpoints/RecipePoint.js"; import { errorHandler } from "./middleware/errorHandler.js"; import { authentication } from "./middleware/authenticationMiddleware.js"; @@ -32,7 +32,7 @@ async function startServer() { // Setup routes app.use(authBasicRoute, authRoutes); app.use("/user", userRoutes); - // app.use("/recipe", recipeRoutes); + app.use("/recipe", recipeRoutes); // Error handling for all rest-calls // must come last! diff --git a/src/mappers/RecipeDtoEntityMapper.ts b/src/mappers/RecipeDtoEntityMapper.ts index f98cfdb..81d37bf 100644 --- a/src/mappers/RecipeDtoEntityMapper.ts +++ b/src/mappers/RecipeDtoEntityMapper.ts @@ -1,17 +1,34 @@ import { RecipeDto } from "../dtos/RecipeDto.js"; import { RecipeEntity } from "../entities/RecipeEntity.js"; -import { ValidationError } from "../errors/httpErrors.js"; import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; +import { RecipeIngredientGroupDtoEntityMapper } from "./RecipeIngredientGroupDtoEntityMapper.js"; +import { RecipeInstructionStepDtoEntityMapper } from "./RecipeInstructionStepDtoEntityMapper.js"; export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper{ + constructor( + private instructionStepMapper : RecipeInstructionStepDtoEntityMapper, + private ingredientGroupMapper : RecipeIngredientGroupDtoEntityMapper + ){ + super(); + } + toDto(entity: RecipeEntity): RecipeDto { const dto = new RecipeDto(); this.mapBaseEntityToDto(entity, dto); dto.title = entity.title; dto.amount = entity.amount - dto.amountDescription = dto.amountDescription; + dto.amountDescription = entity.amountDescription; + // map instructions + const instructionStepEntities = entity.instructionSteps; + const instructionStepDtos = instructionStepEntities.map((stepEntity) => this.instructionStepMapper.toDto(stepEntity)); + dto.instructions = instructionStepDtos; + + // map ingredient groups + const ingredientGroupEntities = entity.ingredientGroups; + const ingredientGroupDtos = ingredientGroupEntities.map((groupEntity) => this.ingredientGroupMapper.toDto(groupEntity)); + dto.ingredientGroups = ingredientGroupDtos; return dto; } @@ -20,11 +37,20 @@ export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper this.instructionStepMapper.toEntity(stepDto)); + entity.instructionSteps = instructionStepEntities; + + // map ingredient groups + const ingredientGroupDtos = dto.ingredientGroups; + const ingredientGroupEntities = ingredientGroupDtos.map((ingredientGroupDto) => this.ingredientGroupMapper.toEntity(ingredientGroupDto)); + entity.ingredientGroups = ingredientGroupEntities; + return entity; } diff --git a/src/mappers/RecipeIngredientDtoEntityMapper.ts b/src/mappers/RecipeIngredientDtoEntityMapper.ts new file mode 100644 index 0000000..0a8baab --- /dev/null +++ b/src/mappers/RecipeIngredientDtoEntityMapper.ts @@ -0,0 +1,32 @@ +import { RecipeIngredientDto } from "../dtos/RecipeIngredientDto.js"; +import { RecipeIngredientEntity } from "../entities/RecipeIngredientEntity.js"; +import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; + +export class RecipeIngredientDtoEntityMapper extends AbstractDtoEntityMapper{ + + toDto(entity: RecipeIngredientEntity): RecipeIngredientDto { + const dto = new RecipeIngredientDto(); + this.mapBaseEntityToDto(entity, dto); + + dto.amount = entity.amount; + dto.name = entity.name; + dto.unit = entity.unit; + dto.sortOrder = entity.sortOrder; + dto.subtext = entity.subtext; + + return dto; + } + + toEntity(dto: RecipeIngredientDto): RecipeIngredientEntity { + const entity = new RecipeIngredientEntity(); + this.mapBaseDtoToEntity(dto,entity); + + entity.amount = dto.amount; + entity.name = dto.name; + entity.unit = dto.unit; + entity.sortOrder = dto.sortOrder; + entity.subtext = dto.subtext; + + return entity; + } +} \ No newline at end of file diff --git a/src/mappers/RecipeIngredientGroupDtoEntityMapper.ts b/src/mappers/RecipeIngredientGroupDtoEntityMapper.ts new file mode 100644 index 0000000..a2cfbd3 --- /dev/null +++ b/src/mappers/RecipeIngredientGroupDtoEntityMapper.ts @@ -0,0 +1,43 @@ +import { RecipeIngredientGroupDto } from "../dtos/RecipeIngredientGroupDto.js"; +import { RecipeIngredientGroupEntity } from "../entities/RecipeIngredientGroupEntity.js"; +import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; +import { RecipeIngredientDtoEntityMapper } from "./RecipeIngredientDtoEntityMapper.js"; + +export class RecipeIngredientGroupDtoEntityMapper extends AbstractDtoEntityMapper{ + constructor( + private ingredientMapper : RecipeIngredientDtoEntityMapper + ){ + super(); + } + + toDto(entity: RecipeIngredientGroupEntity): RecipeIngredientGroupDto { + const dto = new RecipeIngredientGroupDto(); + this.mapBaseEntityToDto(entity, dto); + + dto.title = entity.title; + dto.sortOrder = entity.sortOrder + + // map ingredients + const ingredientEntities = entity.ingredients; + const ingredientDtos = ingredientEntities?.map((ingredientEntity) => this.ingredientMapper.toDto(ingredientEntity)); + dto.ingredients = ingredientDtos; + + return dto; + } + + toEntity(dto: RecipeIngredientGroupDto): RecipeIngredientGroupEntity { + const entity = new RecipeIngredientGroupEntity(); + this.mapBaseDtoToEntity(dto, entity); + + entity.title = dto.title; + entity.sortOrder = dto.sortOrder + + // map ingredients + const ingredientDtos = dto.ingredients; + const ingredientEntities = ingredientDtos?.map((ingredientDto) => this.ingredientMapper.toEntity(ingredientDto)); + entity.ingredients = ingredientEntities; + + return entity; + } + +} \ No newline at end of file diff --git a/src/mappers/RecipeInstructionStepDtoEntityMapper.ts b/src/mappers/RecipeInstructionStepDtoEntityMapper.ts new file mode 100644 index 0000000..be7ca3a --- /dev/null +++ b/src/mappers/RecipeInstructionStepDtoEntityMapper.ts @@ -0,0 +1,27 @@ +import { RecipeInstructionStepDto } from "../dtos/RecipeInstructionStepDto.js"; +import { RecipeInstructionStepEntity } from "../entities/RecipeInstructionStepEntity.js"; +import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js"; + +export class RecipeInstructionStepDtoEntityMapper extends AbstractDtoEntityMapper{ + toDto(entity: RecipeInstructionStepEntity): RecipeInstructionStepDto { + const dto = new RecipeInstructionStepDto(); + this.mapBaseEntityToDto(entity, dto); + + dto.text = entity.text; + dto.sortOrder = entity.sortOrder + + return dto; + } + + toEntity(dto: RecipeInstructionStepDto): RecipeInstructionStepEntity { + const entity = new RecipeInstructionStepEntity(); + this.mapBaseDtoToEntity(dto, entity); + + + entity.text = dto.text; + entity.sortOrder = dto.sortOrder + + return entity; + } + +} \ No newline at end of file diff --git a/src/migrations/1759053089684-AddTitleColumnToRecipeTable.ts b/src/migrations/1759053089684-AddTitleColumnToRecipeTable.ts new file mode 100644 index 0000000..db32194 --- /dev/null +++ b/src/migrations/1759053089684-AddTitleColumnToRecipeTable.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddTitleColumnToRecipeTable1759053089684 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + // At title column with not null constraint + await queryRunner.addColumn( + "recipe", + new TableColumn({ + name: "title", + type: "varchar", + isNullable: false + })); + } + + public async down(queryRunner: QueryRunner): Promise { + // Remove title column + await queryRunner.dropColumn("recipe", "title"); + } + +} diff --git a/src/migrations/1759053552602-RenameSortOrderColumns.ts b/src/migrations/1759053552602-RenameSortOrderColumns.ts new file mode 100644 index 0000000..89d8872 --- /dev/null +++ b/src/migrations/1759053552602-RenameSortOrderColumns.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RenameSortOrderColumns1759053552602 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn("recipe_instruction_step", "sortOrder", "sort_order"); + await queryRunner.renameColumn("recipe_ingredient_group", "sortOrder", "sort_order"); + await queryRunner.renameColumn("recipe_ingredient", "sortOrder", "sort_order"); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.renameColumn("recipe_instruction_step", "sort_order", "sortOrder"); + await queryRunner.renameColumn("recipe_ingredient_group", "sort_order", "sortOrder"); + await queryRunner.renameColumn("recipe_ingredient", "sort_order", "sortOrder"); + } + +}