Skip to content

Commit

Permalink
Merge pull request #3 from 4bitWise/dev
Browse files Browse the repository at this point in the history
generate a list of ingredients based on the ids of recipes
  • Loading branch information
linerol authored Dec 2, 2024
2 parents 7ff6080 + 6f16dbe commit f11c83a
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 44 deletions.
15 changes: 5 additions & 10 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,18 +17,13 @@ export class CreateIngredientDto {
@Type(() => Number)
price: number;

@ApiProperty()
@IsPositive()
@Type(() => Number)
quantity: number;

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

@ApiProperty()
@ApiProperty({ example: 'baking', description: 'Ingredient category' })
@IsString()
@IsOptional()
@IsOptional()
category?: string;
}

Expand Down
4 changes: 2 additions & 2 deletions src/dtos/measureunit/measureunit.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export class CreateMeasureunitDto {

@ApiProperty()
@IsPositive()
@Type(() => Number)
measurement_type: number;
@Type(() => String)
measurement_type: string;

@ApiProperty()
@IsPositive()
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) {}
4 changes: 1 addition & 3 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 All @@ -14,9 +15,6 @@ export class Ingredient {
@Prop({ required: true })
price: number;

@Prop({ required: true })
quantity: number;

@Prop({ required: true })
unit_id: string;

Expand Down
4 changes: 2 additions & 2 deletions src/schemas/measureunit/measureunit.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class Measureunit {
@Prop({ required: true, type: String })
unit_symbol: string;

@Prop({ required: true, type: Number })
measurement_type: number;
@Prop({ required: true, type: String })
measurement_type: string;

@Prop({ required: true, type: Number })
conversion_factor: number;
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
29 changes: 21 additions & 8 deletions src/services/ingredient/ingredient/ingredients.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@ import {
HttpException,
} from '@nestjs/common';
import { IngredientsService } from './ingredients.service';
import { CreateIngredientDto, UpdateIngredientDto } from 'src/dtos/ingredient/ingredient.dto';
import {
CreateIngredientDto,
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 @@ -38,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 @@ -58,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 @@ -76,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
1 change: 0 additions & 1 deletion src/services/ingredient/ingredient/ingredients.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {

@Injectable()
export class IngredientsService {

constructor(
@InjectModel(Ingredient.name)
private ingredientModel: Model<IngredientDocument>,
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 f11c83a

Please sign in to comment.