save recipes
This commit is contained in:
parent
3a887d8dbb
commit
380eb4cd21
18 changed files with 412 additions and 87 deletions
62
bruno/recipe-backend/createRecipe.bru
Normal file
62
bruno/recipe-backend/createRecipe.bru
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
19
bruno/recipe-backend/getAllRecipes.bru
Normal file
19
bruno/recipe-backend/getAllRecipes.bru
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -1,76 +1,129 @@
|
||||||
import { Entity } from "typeorm";
|
|
||||||
import { RecipeDto } from "../dtos/RecipeDto.js";
|
import { RecipeDto } from "../dtos/RecipeDto.js";
|
||||||
import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js";
|
import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js";
|
||||||
import { RecipeRepository } from "../repositories/RecipeRepository.js";
|
import { RecipeRepository } from "../repositories/RecipeRepository.js";
|
||||||
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
||||||
import { ValidationError } from "../errors/httpErrors.js";
|
import { ValidationError } from "../errors/httpErrors.js";
|
||||||
import { RecipeInstructionStepDto } from "../dtos/RecipeInstructionStepDto.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 user specific actions
|
||||||
*/
|
*/
|
||||||
export class RecipeController {
|
export class RecipeController {
|
||||||
constructor(
|
constructor(
|
||||||
private repository: RecipeRepository,
|
private repository: RecipeRepository,
|
||||||
private mapper: RecipeDtoEntityMapper
|
private mapper: RecipeDtoEntityMapper
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load list of all recipes
|
* Load list of all recipes
|
||||||
* @returns List of all recipes
|
* @returns List of all recipes
|
||||||
*/
|
*/
|
||||||
async getAllRecipes(){
|
async getAllRecipes() {
|
||||||
const recipeEntities = await this.repository.findAll();
|
const recipeEntities : RecipeEntity[] = await this.repository.findAll();
|
||||||
let recipeDtos : RecipeDto[] = [];
|
let recipeDtos: RecipeDto[] = [];
|
||||||
recipeEntities.forEach(recipeEntity => {
|
recipeEntities.forEach(recipeEntity => {
|
||||||
recipeDtos.push(this.mapper.toDto(recipeEntity));
|
recipeDtos.push(this.mapper.toDto(recipeEntity));
|
||||||
});
|
});
|
||||||
return recipeDtos;
|
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!")
|
|
||||||
}
|
}
|
||||||
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;
|
* Create a new recipe
|
||||||
if(dto.title == undefined
|
* @param dto RecipeDto containing the new recipe
|
||||||
|| dto.title.length == 0
|
*/
|
||||||
|| dto.amount == undefined
|
async createRecipe(dto: RecipeDto) {
|
||||||
|| dto.amountDescription == undefined
|
if (!this.isRecipeDtoValid(dto)) {
|
||||||
|| dto.amountDescription.length == 0
|
throw new ValidationError("recipe data is not valid!")
|
||||||
|| !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;
|
|
||||||
}
|
}
|
||||||
});
|
const recipeEntity = this.mapper.toEntity(dto)
|
||||||
|
const entity = await this.repository.create(recipeEntity);
|
||||||
return isValid;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,6 @@ export class RecipeDto extends AbstractDto {
|
||||||
title!: string;
|
title!: string;
|
||||||
amount?: number
|
amount?: number
|
||||||
amountDescription?: string;
|
amountDescription?: string;
|
||||||
instructions?: RecipeInstructionStepDto[];
|
instructions!: RecipeInstructionStepDto[];
|
||||||
ingredientGroups?: RecipeIngredientGroupDto[];
|
ingredientGroups!: RecipeIngredientGroupDto[];
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@ import { UUID } from "crypto";
|
||||||
import { AbstractDto } from "./AbstractDto.js";
|
import { AbstractDto } from "./AbstractDto.js";
|
||||||
|
|
||||||
export class RecipeIngredientDto extends AbstractDto{
|
export class RecipeIngredientDto extends AbstractDto{
|
||||||
name?: string;
|
name!: string;
|
||||||
subtext?: string;
|
subtext?: string;
|
||||||
amount?: number;
|
amount?: number;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
sortOrder?: number;
|
sortOrder!: number;
|
||||||
ingredientGroupId?: UUID;
|
ingredientGroupId?: UUID;
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { RecipeIngredientDto } from "./RecipeIngredientDto.js";
|
||||||
|
|
||||||
export class RecipeIngredientGroupDto extends AbstractDto{
|
export class RecipeIngredientGroupDto extends AbstractDto{
|
||||||
title?: string;
|
title?: string;
|
||||||
sortOrder?: string;
|
sortOrder!: number;
|
||||||
recipeId?: UUID;
|
recipeId?: UUID;
|
||||||
ingredients?: RecipeIngredientDto[];
|
ingredients!: RecipeIngredientDto[];
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { UUID } from "crypto";
|
||||||
import { AbstractDto } from "./AbstractDto.js";
|
import { AbstractDto } from "./AbstractDto.js";
|
||||||
|
|
||||||
export class RecipeInstructionStepDto extends AbstractDto{
|
export class RecipeInstructionStepDto extends AbstractDto{
|
||||||
text?: string;
|
text!: string;
|
||||||
sortOrder?: number;
|
sortOrder!: number;
|
||||||
recipeId?: UUID;
|
recipeId?: UUID;
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,10 @@ import { RecipeRepository } from "../repositories/RecipeRepository.js";
|
||||||
import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js";
|
import { RecipeDtoEntityMapper } from "../mappers/RecipeDtoEntityMapper.js";
|
||||||
import { RecipeController } from "../controllers/RecipeController.js";
|
import { RecipeController } from "../controllers/RecipeController.js";
|
||||||
import { asyncHandler } from "../utils/asyncHandler.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
|
* Handles all user related routes
|
||||||
|
|
@ -11,16 +15,34 @@ const router = Router();
|
||||||
|
|
||||||
// Inject repo + mapper here
|
// Inject repo + mapper here
|
||||||
const recipeRepository = new RecipeRepository();
|
const recipeRepository = new RecipeRepository();
|
||||||
const recipeDtoEntityMapper = new RecipeDtoEntityMapper();
|
const recipeIngredientMapper = new RecipeIngredientDtoEntityMapper();
|
||||||
const recipeController = new RecipeController(recipeRepository, recipeDtoEntityMapper);
|
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(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
const response = await recipeController.getAllRecipes();
|
const response = await recipeController.getAllRecipes();
|
||||||
res.status(201).json(response);
|
res.status(201).json(response);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -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 { AbstractEntity } from "./AbstractEntity.js";
|
||||||
import { RecipeIngredientGroupEntity } from "./RecipeIngredientGroupEntity.js";
|
import { RecipeIngredientGroupEntity } from "./RecipeIngredientGroupEntity.js";
|
||||||
|
|
||||||
|
|
@ -19,11 +19,12 @@ export class RecipeIngredientEntity extends AbstractEntity {
|
||||||
@Column({nullable: true})
|
@Column({nullable: true})
|
||||||
unit?: string;
|
unit?: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ name: "sort_order", nullable: false })
|
||||||
sortOrder!: number;
|
sortOrder!: number;
|
||||||
|
|
||||||
|
@JoinColumn({name: "recipe_ingredient_group_id"})
|
||||||
@ManyToOne(() => RecipeIngredientGroupEntity, (ingredientGroup) => ingredientGroup.ingredients,
|
@ManyToOne(() => RecipeIngredientGroupEntity, (ingredientGroup) => ingredientGroup.ingredients,
|
||||||
{onDelete: "CASCADE"}
|
{onDelete: "CASCADE", nullable: false}
|
||||||
)
|
)
|
||||||
ingredientGroup!: Relation<RecipeIngredientGroupEntity>;
|
ingredientGroup!: Relation<RecipeIngredientGroupEntity>;
|
||||||
}
|
}
|
||||||
|
|
@ -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 { AbstractEntity } from "./AbstractEntity.js";
|
||||||
import { RecipeEntity } from "./RecipeEntity.js";
|
import { RecipeEntity } from "./RecipeEntity.js";
|
||||||
import { RecipeIngredientEntity } from "./RecipeIngredientEntity.js";
|
import { RecipeIngredientEntity } from "./RecipeIngredientEntity.js";
|
||||||
|
|
@ -11,11 +11,12 @@ export class RecipeIngredientGroupEntity extends AbstractEntity {
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ name: "sort_order", nullable: false })
|
||||||
sortOrder!: number;
|
sortOrder!: number;
|
||||||
|
|
||||||
|
@JoinColumn({name: "recipe_id"})
|
||||||
@ManyToOne(() => RecipeEntity, (recipe) => recipe.ingredientGroups,
|
@ManyToOne(() => RecipeEntity, (recipe) => recipe.ingredientGroups,
|
||||||
{onDelete: "CASCADE"}
|
{onDelete: "CASCADE", nullable: false}
|
||||||
)
|
)
|
||||||
recipe!: Relation<RecipeEntity>;
|
recipe!: Relation<RecipeEntity>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { AbstractEntity } from "./AbstractEntity.js";
|
||||||
import { RecipeEntity } from "./RecipeEntity.js";
|
import { RecipeEntity } from "./RecipeEntity.js";
|
||||||
|
|
||||||
|
|
@ -10,11 +10,12 @@ export class RecipeInstructionStepEntity extends AbstractEntity {
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
text!: string;
|
text!: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ name: "sort_order", nullable: false })
|
||||||
sortOrder!: number;
|
sortOrder!: number;
|
||||||
|
|
||||||
|
@JoinColumn({name: "recipe_id"})
|
||||||
@ManyToOne(() => RecipeEntity, (recipe) => recipe.instructionSteps,
|
@ManyToOne(() => RecipeEntity, (recipe) => recipe.instructionSteps,
|
||||||
{onDelete: "CASCADE"}
|
{onDelete: "CASCADE", nullable: false}
|
||||||
)
|
)
|
||||||
recipe!: Relation<RecipeEntity>;
|
recipe!: Relation<RecipeEntity>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import dotenv from "dotenv";
|
||||||
import { AppDataSource } from "./data-source.js";
|
import { AppDataSource } from "./data-source.js";
|
||||||
import authRoutes, { authBasicRoute } from "./endpoints/AuthPoint.js";
|
import authRoutes, { authBasicRoute } from "./endpoints/AuthPoint.js";
|
||||||
import userRoutes from "./endpoints/UserPoint.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 { errorHandler } from "./middleware/errorHandler.js";
|
||||||
import { authentication } from "./middleware/authenticationMiddleware.js";
|
import { authentication } from "./middleware/authenticationMiddleware.js";
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ async function startServer() {
|
||||||
// Setup routes
|
// Setup routes
|
||||||
app.use(authBasicRoute, authRoutes);
|
app.use(authBasicRoute, authRoutes);
|
||||||
app.use("/user", userRoutes);
|
app.use("/user", userRoutes);
|
||||||
// app.use("/recipe", recipeRoutes);
|
app.use("/recipe", recipeRoutes);
|
||||||
|
|
||||||
// Error handling for all rest-calls
|
// Error handling for all rest-calls
|
||||||
// must come last!
|
// must come last!
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,34 @@
|
||||||
import { RecipeDto } from "../dtos/RecipeDto.js";
|
import { RecipeDto } from "../dtos/RecipeDto.js";
|
||||||
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
import { RecipeEntity } from "../entities/RecipeEntity.js";
|
||||||
import { ValidationError } from "../errors/httpErrors.js";
|
|
||||||
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js";
|
import { AbstractDtoEntityMapper } from "./AbstractDtoEntityMapper.js";
|
||||||
|
import { RecipeIngredientGroupDtoEntityMapper } from "./RecipeIngredientGroupDtoEntityMapper.js";
|
||||||
|
import { RecipeInstructionStepDtoEntityMapper } from "./RecipeInstructionStepDtoEntityMapper.js";
|
||||||
|
|
||||||
export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity,RecipeDto>{
|
export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity,RecipeDto>{
|
||||||
|
constructor(
|
||||||
|
private instructionStepMapper : RecipeInstructionStepDtoEntityMapper,
|
||||||
|
private ingredientGroupMapper : RecipeIngredientGroupDtoEntityMapper
|
||||||
|
){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
toDto(entity: RecipeEntity): RecipeDto {
|
toDto(entity: RecipeEntity): RecipeDto {
|
||||||
const dto = new RecipeDto();
|
const dto = new RecipeDto();
|
||||||
this.mapBaseEntityToDto(entity, dto);
|
this.mapBaseEntityToDto(entity, dto);
|
||||||
|
|
||||||
dto.title = entity.title;
|
dto.title = entity.title;
|
||||||
dto.amount = entity.amount
|
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;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
@ -20,11 +37,20 @@ export class RecipeDtoEntityMapper extends AbstractDtoEntityMapper<RecipeEntity,
|
||||||
const entity = new RecipeEntity();
|
const entity = new RecipeEntity();
|
||||||
this.mapBaseDtoToEntity(dto, entity);
|
this.mapBaseDtoToEntity(dto, entity);
|
||||||
|
|
||||||
|
|
||||||
entity.title = dto.title;
|
entity.title = dto.title;
|
||||||
entity.amount = dto.amount
|
entity.amount = dto.amount
|
||||||
entity.amountDescription = dto.amountDescription;
|
entity.amountDescription = dto.amountDescription;
|
||||||
|
|
||||||
|
// map instructions
|
||||||
|
const instructionStepDtos = dto.instructions;
|
||||||
|
const instructionStepEntities = instructionStepDtos.map((stepDto) => 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;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
src/mappers/RecipeIngredientDtoEntityMapper.ts
Normal file
32
src/mappers/RecipeIngredientDtoEntityMapper.ts
Normal file
|
|
@ -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<RecipeIngredientEntity, RecipeIngredientDto>{
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/mappers/RecipeIngredientGroupDtoEntityMapper.ts
Normal file
43
src/mappers/RecipeIngredientGroupDtoEntityMapper.ts
Normal file
|
|
@ -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<RecipeIngredientGroupEntity,RecipeIngredientGroupDto>{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
src/mappers/RecipeInstructionStepDtoEntityMapper.ts
Normal file
27
src/mappers/RecipeInstructionStepDtoEntityMapper.ts
Normal file
|
|
@ -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<RecipeInstructionStepEntity,RecipeInstructionStepDto>{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/migrations/1759053089684-AddTitleColumnToRecipeTable.ts
Normal file
21
src/migrations/1759053089684-AddTitleColumnToRecipeTable.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
|
||||||
|
|
||||||
|
export class AddTitleColumnToRecipeTable1759053089684 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// 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<void> {
|
||||||
|
// Remove title column
|
||||||
|
await queryRunner.dropColumn("recipe", "title");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
17
src/migrations/1759053552602-RenameSortOrderColumns.ts
Normal file
17
src/migrations/1759053552602-RenameSortOrderColumns.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class RenameSortOrderColumns1759053552602 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue