Skip to content

Commit

Permalink
feat(recipe): generate a list of ingredients based on the ids of recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
linerol committed Dec 2, 2024
1 parent 87f745d commit 6f16dbe
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 30 deletions.
8 changes: 4 additions & 4 deletions src/dtos/ingredient/ingredient.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { Type } from 'class-transformer';
import { PartialType } from '@nestjs/swagger';

export class CreateIngredientDto {
@ApiProperty()
@ApiProperty({ example: 'ingredient_flour', description: 'Ingredient ID' })
@IsString()
id: string;

@ApiProperty()
@ApiProperty({ example: 'Flour', description: 'Ingredient name' })
@IsString()
name: string;

Expand All @@ -17,11 +17,11 @@ export class CreateIngredientDto {
@Type(() => Number)
price: number;

@ApiProperty()
@ApiProperty({ example: 'weight_g' })
@IsString()
unit_id: string;

@ApiProperty()
@ApiProperty({ example: 'baking', description: 'Ingredient category' })
@IsString()
@IsOptional()
category?: string;
Expand Down
15 changes: 10 additions & 5 deletions src/dtos/recipe/recipe.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { IsString, IsNumber, IsArray, ValidateNested, IsPositive } from 'class-validator';
import {
IsString,
IsNumber,
IsArray,
ValidateNested,
IsPositive,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
import { PartialType } from '@nestjs/swagger';
import { ApiProperty, PartialType } from '@nestjs/swagger';

class IngredientDetailDto {
export class IngredientDetailDto {
@ApiProperty()
@IsString()
ingredient_id: string;
Expand Down Expand Up @@ -54,4 +59,4 @@ export class CreateRecipeDto {
instructions: string[];
}

export class UpdateRecipeDto extends PartialType(CreateRecipeDto) {}
export class UpdateRecipeDto extends PartialType(CreateRecipeDto) {}
1 change: 1 addition & 0 deletions src/schemas/ingredient/ingredient.schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ApiProperty } from '@nestjs/swagger';
import { HydratedDocument } from 'mongoose';

export type IngredientDocument = HydratedDocument<Ingredient>;
Expand Down
5 changes: 1 addition & 4 deletions src/schemas/recipe/recipe.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class IngredientDetail {
unit_id: string;
}

@Schema({ strict: false, timestamps: true })
@Schema({ timestamps: true })
export class Recipe {
@Prop({ required: true, unique: true })
id: string;
Expand All @@ -29,9 +29,6 @@ export class Recipe {
@Prop({ required: true, type: Number })
servings: number;

@Prop({ required: true, type: Number })
cost: number;

@Prop({ required: true, type: [IngredientDetail] })
ingredients: IngredientDetail[];

Expand Down
26 changes: 18 additions & 8 deletions src/services/ingredient/ingredient/ingredients.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Body,
Param,
Put,
Delete,
Delete,
HttpStatus,
HttpException,
} from '@nestjs/common';
Expand All @@ -15,20 +15,22 @@ import {
UpdateIngredientDto,
} from 'src/dtos/ingredient/ingredient.dto';
import { Ingredient } from 'src/schemas/ingredient/ingredient.schema';
import { ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';

@ApiTags('ingredients')
@Controller('ingredients')
export class IngredientsController {
constructor(private readonly IngredientsService: IngredientsService) {}
constructor(private readonly ingredientsService: IngredientsService) {}

@ApiOperation({ summary: 'Create an ingredient' })
@ApiBody({ type: CreateIngredientDto })
@Post()
async create(
@Body() createIngredientDto: CreateIngredientDto,
): Promise<Ingredient> {
try {
const newIngredient =
await this.IngredientsService.create(createIngredientDto);
await this.ingredientsService.create(createIngredientDto);
return newIngredient;
} catch (err) {
throw new HttpException(
Expand All @@ -41,15 +43,18 @@ export class IngredientsController {
}
}

@ApiOperation({ summary: 'Find all ingredients' })
@Get()
async findAll(): Promise<Ingredient[]> {
return this.IngredientsService.findAll();
return this.ingredientsService.findAll();
}

@ApiOperation({ summary: 'Find one ingredient' })
@ApiParam({ name: 'id', description: 'Ingredient ID' })
@Get(':id')
async findOne(@Param('id') id: string): Promise<Ingredient> {
try {
return this.IngredientsService.findOne(id);
return this.ingredientsService.findOne(id);
} catch (err) {
throw new HttpException(
{
Expand All @@ -61,13 +66,16 @@ export class IngredientsController {
}
}

@ApiOperation({ summary: 'Update a ingredient' })
@ApiBody({ type: UpdateIngredientDto })
@ApiParam({ name: 'id', description: 'Ingredient ID' })
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateIngredientDto: UpdateIngredientDto,
): Promise<Ingredient> {
try {
return this.IngredientsService.update(id, updateIngredientDto);
return this.ingredientsService.update(id, updateIngredientDto);
} catch (err) {
throw new HttpException(
{
Expand All @@ -79,10 +87,12 @@ export class IngredientsController {
}
}

@ApiOperation({ summary: 'Delete an ingredient' })
@ApiParam({ name: 'id', description: 'Ingredient ID' })
@Delete(':id')
async remove(@Param('id') id: string): Promise<Ingredient> {
try {
return this.IngredientsService.remove(id);
return this.ingredientsService.remove(id);
} catch (err) {
throw new HttpException(
{
Expand Down
49 changes: 41 additions & 8 deletions src/services/recipe/recipes.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,25 @@ import {
HttpException,
} from '@nestjs/common';
import { RecipesService } from './recipes.service';
import { CreateRecipeDto, UpdateRecipeDto } from 'src/dtos/recipe/recipe.dto';
import {
CreateRecipeDto,
IngredientDetailDto,
UpdateRecipeDto,
} from 'src/dtos/recipe/recipe.dto';
import { Recipe } from 'src/schemas/recipe/recipe.schema';
import { ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';

@ApiTags('recipes')
@Controller('recipes')
export class RecipesController {
constructor(private readonly RecipesService: RecipesService) {}
constructor(private readonly recipesService: RecipesService) {}

@ApiOperation({ summary: 'Create a new recipe' })
@ApiBody({ type: CreateRecipeDto })
@Post()
async create(@Body() createRecipeDto: CreateRecipeDto): Promise<Recipe> {
try {
return await this.RecipesService.create(createRecipeDto);
return await this.recipesService.create(createRecipeDto);
} catch (err) {
throw new HttpException(
{
Expand All @@ -34,15 +40,18 @@ export class RecipesController {
}
}

@ApiOperation({ summary: 'Find all recipes' })
@Get()
async findAll(): Promise<Recipe[]> {
return this.RecipesService.findAll();
return this.recipesService.findAll();
}

@ApiOperation({ summary: 'Find a recipe by id' })
@Get(':id')
@ApiParam({ name: 'id', description: 'Recipe ID' })
async findOne(@Param('id') id: string): Promise<Recipe> {
try {
return await this.RecipesService.findOne(id);
return await this.recipesService.findOne(id);
} catch (err) {
throw new HttpException(
{
Expand All @@ -54,13 +63,16 @@ export class RecipesController {
}
}

@ApiOperation({ summary: 'Update a recipe' })
@ApiBody({ type: UpdateRecipeDto })
@ApiParam({ name: 'id', description: 'Recipe ID' })
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateRecipeDto: UpdateRecipeDto,
): Promise<Recipe> {
try {
return await this.RecipesService.update(id, updateRecipeDto);
return await this.recipesService.update(id, updateRecipeDto);
} catch (err) {
throw new HttpException(
{
Expand All @@ -72,10 +84,12 @@ export class RecipesController {
}
}

@ApiParam({ name: 'id', description: 'Recipe ID' })
@ApiOperation({ summary: 'Delete a recipe' })
@Delete(':id')
async remove(@Param('id') id: string): Promise<Recipe> {
try {
return await this.RecipesService.remove(id);
return await this.recipesService.remove(id);
} catch (err) {
throw new HttpException(
{
Expand All @@ -86,4 +100,23 @@ export class RecipesController {
);
}
}

@ApiBody({ type: IngredientDetailDto })
@ApiOperation({ summary: 'Find all ingredients based on many recipes' })
@Post('generate-ingredients-list')
async generateIngredientsList(
@Body() recipeIds: string[],
): Promise<IngredientDetailDto[]> {
try {
return await this.recipesService.generateIngredientsList(recipeIds);
} catch (err) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'Error generating ingredients list!',
},
HttpStatus.BAD_REQUEST,
);
}
}
}
31 changes: 30 additions & 1 deletion src/services/recipe/recipes.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Recipe, RecipeDocument } from 'src/schemas/recipe/recipe.schema';
import { CreateRecipeDto, UpdateRecipeDto } from 'src/dtos/recipe/recipe.dto';
import {
CreateRecipeDto,
IngredientDetailDto,
UpdateRecipeDto,
} from 'src/dtos/recipe/recipe.dto';

@Injectable()
export class RecipesService {
Expand Down Expand Up @@ -46,4 +50,29 @@ export class RecipesService {
}
return deletedRecipe;
}

async generateIngredientsList(
recipeIds: string[],
): Promise<IngredientDetailDto[]> {
const ingredientsMap: { [key: string]: IngredientDetailDto } = {};
for (const recipeId of recipeIds) {
const recipe = await this.recipeModel.findOne({ id: recipeId }).exec();
if (!recipe) {
throw new NotFoundException(`Recipe with ID ${recipeId} not found`);
}
for (const ingredient of recipe.ingredients) {
const key = `${ingredient.ingredient_id}-${ingredient.unit_id}`;
if (ingredientsMap[key]) {
ingredientsMap[key].quantity += ingredient.quantity;
} else {
ingredientsMap[key] = {
ingredient_id: ingredient.ingredient_id,
quantity: ingredient.quantity,
unit_id: ingredient.unit_id,
};
}
}
}
return Object.values(ingredientsMap);
}
}

0 comments on commit 6f16dbe

Please sign in to comment.