diff --git a/api/apps/api/src/app.module.ts b/api/apps/api/src/app.module.ts index 3f2ed87..54ea587 100644 --- a/api/apps/api/src/app.module.ts +++ b/api/apps/api/src/app.module.ts @@ -5,6 +5,7 @@ import { UserModule } from '@app/user/user.module'; import { AuthModule } from '@app/auth/auth.module'; import { ImageModule } from '@app/image/image.module'; import { IngredientModule } from '@app/ingredient/ingredient.module'; +import { RecipeModule } from '@app/recipe/recipe.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { IngredientModule } from '@app/ingredient/ingredient.module'; AuthModule, ImageModule, IngredientModule, + RecipeModule, ], }) export class AppModule {} diff --git a/api/apps/mono_lambda/src/mono_lambda.module.ts b/api/apps/mono_lambda/src/mono_lambda.module.ts index dc94646..6d2d688 100644 --- a/api/apps/mono_lambda/src/mono_lambda.module.ts +++ b/api/apps/mono_lambda/src/mono_lambda.module.ts @@ -5,6 +5,7 @@ import { UserModule } from '@app/user/user.module'; import { AuthModule } from '@app/auth/auth.module'; import { ImageModule } from '@app/image/image.module'; import { IngredientModule } from '@app/ingredient/ingredient.module'; +import { RecipeModule } from '@app/recipe/recipe.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { IngredientModule } from '@app/ingredient/ingredient.module'; AuthModule, ImageModule, IngredientModule, + RecipeModule, ], }) export class MonoLambdaModule {} diff --git a/api/libs/ingredient/src/controllers/ingredient.controller.spec.ts b/api/libs/ingredient/src/controllers/user-ingredient.controller.spec.ts similarity index 51% rename from api/libs/ingredient/src/controllers/ingredient.controller.spec.ts rename to api/libs/ingredient/src/controllers/user-ingredient.controller.spec.ts index 6fca559..c0b49d7 100644 --- a/api/libs/ingredient/src/controllers/ingredient.controller.spec.ts +++ b/api/libs/ingredient/src/controllers/user-ingredient.controller.spec.ts @@ -1,11 +1,13 @@ import { TestBed } from '@automock/jest'; -import { IngredientController } from './ingredient.controller'; +import { UserIngredientController } from './user-ingredient.controller'; describe('IngredientController', () => { - let controller: IngredientController; + let controller: UserIngredientController; beforeEach(async () => { - const { unit, unitRef } = TestBed.create(IngredientController).compile(); + const { unit, unitRef } = TestBed.create( + UserIngredientController, + ).compile(); controller = unit; }); diff --git a/api/libs/ingredient/src/controllers/ingredient.controller.ts b/api/libs/ingredient/src/controllers/user-ingredient.controller.ts similarity index 77% rename from api/libs/ingredient/src/controllers/ingredient.controller.ts rename to api/libs/ingredient/src/controllers/user-ingredient.controller.ts index ed4bd83..75e9b8b 100644 --- a/api/libs/ingredient/src/controllers/ingredient.controller.ts +++ b/api/libs/ingredient/src/controllers/user-ingredient.controller.ts @@ -21,27 +21,27 @@ import { } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; import { - CreateIngredientResponseDto, - FindAllIngredientResponseDto, - FindOneIngredientResponseDto, - UpdateIngredientResponseDto, + CreateUserIngredientResponseDto, + FindAllUserIngredientResponseDto, + FindOneUserIngredientResponseDto, + UpdateUserIngredientResponseDto, } from '../dto/ingredient-response.dto'; import { - CreateIngredientDto, - UpdateIngredientDto, + CreateUserIngredientDto, + UpdateUserIngredientDto, } from '../dto/modify-ingredient.dto'; -import { IngredientService } from '../services/ingredient.service'; +import { UserIngredientService } from '../services/user-ingredient.service'; import { FoodType } from '../types/food-type.enum'; import { StoreMethod } from '../types/store-method.enum'; -import { FilterIngredientDto } from '../dto/filter-ingredient.dto'; +import { FilterUserIngredientDto } from '../dto/filter-ingredient.dto'; import { queryBuilder } from '@app/common/utils/query-builder'; import { ReqUser } from '@app/common/decorators/req-user.decorator'; import { User } from '@app/user/entities/user.entity'; @ApiTags('Ingredient') @Controller('ingredient') -export class IngredientController { - constructor(private readonly ingredientService: IngredientService) {} +export class UserIngredientController { + constructor(private readonly ingredientService: UserIngredientService) {} /** * ## Create Ingredient @@ -49,10 +49,10 @@ export class IngredientController { * Create a new ingredient with given dto. */ @Auth() - @ApiPostCreated(CreateIngredientResponseDto) + @ApiPostCreated(CreateUserIngredientResponseDto) @Post() async create( - @Body() createIngredientDto: CreateIngredientDto, + @Body() createIngredientDto: CreateUserIngredientDto, @ReqUser() user: User, ) { createIngredientDto.user_id = user.id.toString(); @@ -79,7 +79,7 @@ export class IngredientController { required: false, }) @Auth() - @ApiGet(FindAllIngredientResponseDto) + @ApiGet(FindAllUserIngredientResponseDto) @Get() async findAll( @Query( @@ -96,7 +96,7 @@ export class IngredientController { store_method: StoreMethod, @ReqUser() user: User, ) { - const filterIngredientDto: FilterIngredientDto = queryBuilder({ + const filterIngredientDto: FilterUserIngredientDto = queryBuilder({ food_type, store_method, user_id: user.id.toString(), @@ -110,7 +110,7 @@ export class IngredientController { * Find one ingredient with given id. */ @Auth() - @ApiGet(FindOneIngredientResponseDto) + @ApiGet(FindOneUserIngredientResponseDto) @Get(':id') async findOne(@Param('id') id: string) { return this.ingredientService.findOne(id); @@ -122,11 +122,11 @@ export class IngredientController { * Update ingredient with given id and dto. */ @Auth() - @ApiPatch(UpdateIngredientResponseDto) + @ApiPatch(UpdateUserIngredientResponseDto) @Patch(':id') async update( @Param('id') id: string, - @Body() updateIngredientDto: UpdateIngredientDto, + @Body() updateIngredientDto: UpdateUserIngredientDto, ) { return this.ingredientService.update(id, updateIngredientDto); } diff --git a/api/libs/ingredient/src/dto/filter-ingredient.dto.ts b/api/libs/ingredient/src/dto/filter-ingredient.dto.ts index 37566dc..64bb31c 100644 --- a/api/libs/ingredient/src/dto/filter-ingredient.dto.ts +++ b/api/libs/ingredient/src/dto/filter-ingredient.dto.ts @@ -11,7 +11,7 @@ import { import { FoodType } from '../types/food-type.enum'; import { StoreMethod } from '../types/store-method.enum'; -export class FilterIngredientDto { +export class FilterUserIngredientDto { @IsString() @IsNotEmpty() @IsOptional() diff --git a/api/libs/ingredient/src/dto/ingredient-response.dto.ts b/api/libs/ingredient/src/dto/ingredient-response.dto.ts index 23830ac..d75ce4f 100644 --- a/api/libs/ingredient/src/dto/ingredient-response.dto.ts +++ b/api/libs/ingredient/src/dto/ingredient-response.dto.ts @@ -2,20 +2,20 @@ import { CreatedResponse, OkResponse, } from '@app/common/dto/success-response.dto'; -import { Ingredient } from '../entities/ingredient.entity'; +import { UserIngredient } from '../entities/user-ingredient.entity'; -export class CreateIngredientResponseDto extends CreatedResponse { - data: Ingredient; +export class CreateUserIngredientResponseDto extends CreatedResponse { + data: UserIngredient; } -export class FindAllIngredientResponseDto extends OkResponse { - data: Ingredient[]; +export class FindAllUserIngredientResponseDto extends OkResponse { + data: UserIngredient[]; } -export class FindOneIngredientResponseDto extends OkResponse { - data: Ingredient; +export class FindOneUserIngredientResponseDto extends OkResponse { + data: UserIngredient; } -export class UpdateIngredientResponseDto extends OkResponse { - data: Ingredient; +export class UpdateUserIngredientResponseDto extends OkResponse { + data: UserIngredient; } diff --git a/api/libs/ingredient/src/dto/modify-ingredient.dto.ts b/api/libs/ingredient/src/dto/modify-ingredient.dto.ts index 50622ce..55d692a 100644 --- a/api/libs/ingredient/src/dto/modify-ingredient.dto.ts +++ b/api/libs/ingredient/src/dto/modify-ingredient.dto.ts @@ -12,11 +12,16 @@ import { import { FoodType } from '../types/food-type.enum'; import { StoreMethod } from '../types/store-method.enum'; -export class CreateIngredientDto { +export class CreateUserIngredientDto { @IsString() @IsNotEmpty() name: string; + @IsMongoId() + @IsNotEmpty() + @IsOptional() + ingredient_id?: string; + @IsMongoId() @IsNotEmpty() @IsOptional() @@ -40,4 +45,6 @@ export class CreateIngredientDto { days_before_expiration: number; } -export class UpdateIngredientDto extends PartialType(CreateIngredientDto) {} +export class UpdateUserIngredientDto extends PartialType( + CreateUserIngredientDto, +) {} diff --git a/api/libs/ingredient/src/entities/ingredient.entity.ts b/api/libs/ingredient/src/entities/ingredient.entity.ts index b3d8353..2520a2c 100644 --- a/api/libs/ingredient/src/entities/ingredient.entity.ts +++ b/api/libs/ingredient/src/entities/ingredient.entity.ts @@ -1,42 +1,29 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; -import { FoodType } from '../types/food-type.enum'; -import { StoreMethod } from '../types/store-method.enum'; import { schemaOptions } from '@app/common/utils/schema-option'; -import { User } from '@app/user/entities/user.entity'; -import { ConfigService } from '@nestjs/config'; +import { Prop, Schema } from '@nestjs/mongoose'; +import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; export type IngredientDocument = HydratedDocument; @Schema(schemaOptions) export class Ingredient { - @Prop({ required: true, unique: true, auto: true }) - id: MongooseSchema.Types.ObjectId; - - @Prop({ required: true }) - name: string; - - @Prop({ required: true, type: MongooseSchema.Types.ObjectId }) - user_id: MongooseSchema.Types.ObjectId; - - @Prop({ required: true, type: String, enum: FoodType, default: FoodType.ETC }) - food_type: FoodType; - @Prop({ required: true, - type: String, - enum: StoreMethod, - default: StoreMethod.REFRIGERATE, + unique: true, + auto: true, + type: MongooseSchema.Types.ObjectId, }) - store_method: StoreMethod; + id: MongooseSchema.Types.ObjectId; - @Prop({ required: true, type: Number, default: 0 }) - count: number; + @Prop({ required: true, unique: true }) + name: string; - @Prop({ required: true, type: Number, default: 0 }) - days_before_expiration: number; + @Prop({ required: true }) + description: string; + + @Prop({ required: true }) + thumbnail: string; - @Prop({ required: true, type: String }) + @Prop({ required: true }) icon: string; @Prop({ required: false }) @@ -45,24 +32,3 @@ export class Ingredient { @Prop({ required: false }) updated_at: Date; } - -export const IngredientSchema = SchemaFactory.createForClass(Ingredient); - -IngredientSchema.virtual('user', { - ref: User.name, - localField: 'user_id', - foreignField: 'id', - justOne: true, -}); - -export const IngredientSchemaFactory = (configService: ConfigService) => { - const schema = IngredientSchema; - schema.path('icon', { - require: true, - type: String, - default: `https://${configService.get( - 'AWS_S3_IMAGE_MAIN_BUCKET', - )}.s3.ap-northeast-2.amazonaws.com/ingredient-default-icon.svg`, - }); - return schema; -}; diff --git a/api/libs/ingredient/src/entities/user-ingredient.entity.ts b/api/libs/ingredient/src/entities/user-ingredient.entity.ts new file mode 100644 index 0000000..98f0fe3 --- /dev/null +++ b/api/libs/ingredient/src/entities/user-ingredient.entity.ts @@ -0,0 +1,72 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; +import { FoodType } from '../types/food-type.enum'; +import { StoreMethod } from '../types/store-method.enum'; +import { schemaOptions } from '@app/common/utils/schema-option'; +import { User } from '@app/user/entities/user.entity'; +import { ConfigService } from '@nestjs/config'; + +export type UserIngredientDocument = HydratedDocument; + +@Schema(schemaOptions) +export class UserIngredient { + @Prop({ required: true, unique: true, auto: true }) + id: MongooseSchema.Types.ObjectId; + + @Prop({ required: true }) + name: string; + + @Prop({ required: true, type: MongooseSchema.Types.ObjectId }) + ingredient_id: MongooseSchema.Types.ObjectId; + + @Prop({ required: true, type: MongooseSchema.Types.ObjectId }) + user_id: MongooseSchema.Types.ObjectId; + + @Prop({ required: true, type: String, enum: FoodType, default: FoodType.ETC }) + food_type: FoodType; + + @Prop({ + required: true, + type: String, + enum: StoreMethod, + default: StoreMethod.REFRIGERATE, + }) + store_method: StoreMethod; + + @Prop({ required: true, type: Number, default: 0 }) + count: number; + + @Prop({ required: true, type: Number, default: 0 }) + days_before_expiration: number; + + @Prop({ required: true, type: String }) + icon: string; + + @Prop({ required: false }) + created_at: Date; + + @Prop({ required: false }) + updated_at: Date; +} + +export const UserIngredientSchema = + SchemaFactory.createForClass(UserIngredient); + +UserIngredientSchema.virtual('user', { + ref: User.name, + localField: 'user_id', + foreignField: 'id', + justOne: true, +}); + +export const UserIngredientSchemaFactory = (configService: ConfigService) => { + const schema = UserIngredientSchema; + schema.path('icon', { + require: true, + type: String, + default: `https://${configService.get( + 'AWS_S3_IMAGE_MAIN_BUCKET', + )}.s3.ap-northeast-2.amazonaws.com/ingredient-default-icon.svg`, + }); + return schema; +}; diff --git a/api/libs/ingredient/src/ingredient.module.ts b/api/libs/ingredient/src/ingredient.module.ts index cdd57b2..8658ed8 100644 --- a/api/libs/ingredient/src/ingredient.module.ts +++ b/api/libs/ingredient/src/ingredient.module.ts @@ -1,26 +1,26 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; -import { IngredientController } from './controllers/ingredient.controller'; +import { UserIngredientController } from './controllers/user-ingredient.controller'; import { - Ingredient, - IngredientSchemaFactory, -} from './entities/ingredient.entity'; -import { IngredientRepository } from './repositories/ingredient.repository'; -import { IngredientService } from './services/ingredient.service'; + UserIngredient, + UserIngredientSchemaFactory, +} from './entities/user-ingredient.entity'; +import { UserIngredientRepository } from './repositories/user-ingredient.repository'; +import { UserIngredientService } from './services/user-ingredient.service'; import { ConfigService } from '@nestjs/config'; @Module({ imports: [ MongooseModule.forFeatureAsync([ { - name: Ingredient.name, - useFactory: IngredientSchemaFactory, + name: UserIngredient.name, + useFactory: UserIngredientSchemaFactory, inject: [ConfigService], }, ]), ], - controllers: [IngredientController], - providers: [IngredientService, IngredientRepository], - exports: [IngredientService, IngredientRepository], + controllers: [UserIngredientController], + providers: [UserIngredientService, UserIngredientRepository], + exports: [UserIngredientService, UserIngredientRepository], }) export class IngredientModule {} diff --git a/api/libs/ingredient/src/repositories/ingredient.repository.ts b/api/libs/ingredient/src/repositories/ingredient.repository.ts deleted file mode 100644 index 470ef0a..0000000 --- a/api/libs/ingredient/src/repositories/ingredient.repository.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CommonRepository } from '@app/common/common.repository'; -import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; -import { FilterIngredientDto } from '../dto/filter-ingredient.dto'; -import { CreateIngredientDto } from '../dto/modify-ingredient.dto'; -import { Ingredient, IngredientDocument } from '../entities/ingredient.entity'; - -@Injectable() -export class IngredientRepository extends CommonRepository< - Ingredient, - CreateIngredientDto, - any, - FilterIngredientDto -> { - constructor( - @InjectModel(Ingredient.name) - private readonly ingredientModel: Model, - ) { - super(ingredientModel); - } -} diff --git a/api/libs/ingredient/src/repositories/ingredient.repository.spec.ts b/api/libs/ingredient/src/repositories/user-ingredient.repository.spec.ts similarity index 51% rename from api/libs/ingredient/src/repositories/ingredient.repository.spec.ts rename to api/libs/ingredient/src/repositories/user-ingredient.repository.spec.ts index 0fc2c07..5e9809b 100644 --- a/api/libs/ingredient/src/repositories/ingredient.repository.spec.ts +++ b/api/libs/ingredient/src/repositories/user-ingredient.repository.spec.ts @@ -1,11 +1,13 @@ import { TestBed } from '@automock/jest'; -import { IngredientRepository } from './ingredient.repository'; +import { UserIngredientRepository } from './user-ingredient.repository'; describe('IngredientRepository', () => { - let repository: IngredientRepository; + let repository: UserIngredientRepository; beforeEach(async () => { - const { unit, unitRef } = TestBed.create(IngredientRepository).compile(); + const { unit, unitRef } = TestBed.create( + UserIngredientRepository, + ).compile(); repository = unit; }); diff --git a/api/libs/ingredient/src/repositories/user-ingredient.repository.ts b/api/libs/ingredient/src/repositories/user-ingredient.repository.ts new file mode 100644 index 0000000..46a793b --- /dev/null +++ b/api/libs/ingredient/src/repositories/user-ingredient.repository.ts @@ -0,0 +1,25 @@ +import { CommonRepository } from '@app/common/common.repository'; +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { FilterUserIngredientDto } from '../dto/filter-ingredient.dto'; +import { CreateUserIngredientDto } from '../dto/modify-ingredient.dto'; +import { + UserIngredient, + UserIngredientDocument, +} from '../entities/user-ingredient.entity'; + +@Injectable() +export class UserIngredientRepository extends CommonRepository< + UserIngredient, + CreateUserIngredientDto, + any, + FilterUserIngredientDto +> { + constructor( + @InjectModel(UserIngredient.name) + private readonly userIngredientModel: Model, + ) { + super(userIngredientModel); + } +} diff --git a/api/libs/ingredient/src/services/ingredient.service.ts b/api/libs/ingredient/src/services/ingredient.service.ts deleted file mode 100644 index 717fe40..0000000 --- a/api/libs/ingredient/src/services/ingredient.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommonService } from '@app/common/common.service'; -import { Injectable } from '@nestjs/common'; -import { FilterIngredientDto } from '../dto/filter-ingredient.dto'; -import { CreateIngredientDto } from '../dto/modify-ingredient.dto'; -import { Ingredient } from '../entities/ingredient.entity'; -import { IngredientRepository } from '../repositories/ingredient.repository'; - -@Injectable() -export class IngredientService extends CommonService< - Ingredient, - CreateIngredientDto, - any, - FilterIngredientDto -> { - constructor(private readonly ingredientRepository: IngredientRepository) { - super(ingredientRepository); - } -} diff --git a/api/libs/ingredient/src/services/ingredient.service.spec.ts b/api/libs/ingredient/src/services/user-ingredient.service.spec.ts similarity index 54% rename from api/libs/ingredient/src/services/ingredient.service.spec.ts rename to api/libs/ingredient/src/services/user-ingredient.service.spec.ts index dc62087..8bcabd4 100644 --- a/api/libs/ingredient/src/services/ingredient.service.spec.ts +++ b/api/libs/ingredient/src/services/user-ingredient.service.spec.ts @@ -1,11 +1,11 @@ import { TestBed } from '@automock/jest'; -import { IngredientService } from './ingredient.service'; +import { UserIngredientService } from './user-ingredient.service'; describe('IngredientService', () => { - let service: IngredientService; + let service: UserIngredientService; beforeEach(async () => { - const { unit, unitRef } = TestBed.create(IngredientService).compile(); + const { unit, unitRef } = TestBed.create(UserIngredientService).compile(); service = unit; }); diff --git a/api/libs/ingredient/src/services/user-ingredient.service.ts b/api/libs/ingredient/src/services/user-ingredient.service.ts new file mode 100644 index 0000000..6db5f5b --- /dev/null +++ b/api/libs/ingredient/src/services/user-ingredient.service.ts @@ -0,0 +1,20 @@ +import { CommonService } from '@app/common/common.service'; +import { Injectable } from '@nestjs/common'; +import { FilterUserIngredientDto } from '../dto/filter-ingredient.dto'; +import { CreateUserIngredientDto } from '../dto/modify-ingredient.dto'; +import { UserIngredient } from '../entities/user-ingredient.entity'; +import { UserIngredientRepository } from '../repositories/user-ingredient.repository'; + +@Injectable() +export class UserIngredientService extends CommonService< + UserIngredient, + CreateUserIngredientDto, + any, + FilterUserIngredientDto +> { + constructor( + private readonly userIngredientRepository: UserIngredientRepository, + ) { + super(userIngredientRepository); + } +} diff --git a/api/libs/recipe/src/controllers/recipe.controller.spec.ts b/api/libs/recipe/src/controllers/recipe.controller.spec.ts new file mode 100644 index 0000000..1ae861c --- /dev/null +++ b/api/libs/recipe/src/controllers/recipe.controller.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@automock/jest'; +import { RecipeController } from './recipe.controller'; + +describe('RecipeController', () => { + let controller: RecipeController; + + beforeEach(async () => { + const { unit, unitRef } = TestBed.create(RecipeController).compile(); + + controller = unit; + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/api/libs/recipe/src/controllers/recipe.controller.ts b/api/libs/recipe/src/controllers/recipe.controller.ts new file mode 100644 index 0000000..e8be97b --- /dev/null +++ b/api/libs/recipe/src/controllers/recipe.controller.ts @@ -0,0 +1,110 @@ +import { Auth } from '@app/common/decorators/auth.decorator'; +import { + ApiDeleteNoContent, + ApiGet, + ApiPatch, + ApiPostCreated, +} from '@app/common/decorators/http-method.decorator'; +import { ReqUser } from '@app/common/decorators/req-user.decorator'; +import { queryBuilder } from '@app/common/utils/query-builder'; +import { User } from '@app/user/entities/user.entity'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Param, + Patch, + Post, +} from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { FilterRecipeDto } from '../dto/filter-recipe.dto'; +import { CreateRecipeDto, UpdateRecipeDto } from '../dto/modify-recipe.dto'; +import { + CreateRecipeResponseDto, + FindAllRecipeResponseDto, + FindOneRecipeResponseDto, + UpdateRecipeResponseDto, +} from '../dto/recipe-response.dto'; +import { RecipeService } from '../services/recipe.service'; + +@ApiTags('Recipe') +@Controller('recipe') +export class RecipeController { + constructor(private readonly recipeService: RecipeService) {} + + /** + * ## Create Recipe + * + * Create a new Recipe with given dto. + */ + @Auth() + @ApiPostCreated(CreateRecipeResponseDto) + @Post() + async create( + @Body() createRecipeDto: CreateRecipeDto, + @ReqUser() user: User, + ) { + createRecipeDto.owner = user.id.toString(); + return this.recipeService.create(createRecipeDto); + } + + /** + * ## Find Recipes + * + * Find all Recipes with given query. + * #### Recipe's user_id is request user's id by default. + * If any query is not given, return all Recipes of request user's. + */ + @Auth() + @ApiGet(FindAllRecipeResponseDto) + @Get() + async findAll(@ReqUser() user: User) { + const filterRecipeDto: FilterRecipeDto = queryBuilder({ + user_id: user.id.toString(), + }); + return this.recipeService.findAll(filterRecipeDto); + } + + /** + * ## Find one Recipe + * + * Find one Recipe with given id. + */ + @Auth() + @ApiGet(FindOneRecipeResponseDto) + @Get(':id') + async findOne(@Param('id') id: string) { + return this.recipeService.findOne(id); + } + + /** + * ## Update Recipe + * + * Update Recipe with given id and dto. + */ + @Auth() + @ApiPatch(UpdateRecipeResponseDto) + @Patch(':id') + async update( + @Param('id') id: string, + @Body() updateRecipeDto: UpdateRecipeDto, + ) { + return this.recipeService.update(id, updateRecipeDto); + } + + /** + * ## Delete Recipe + * + * Delete Recipe with given id. + */ + @Auth() + @ApiDeleteNoContent() + @HttpCode(HttpStatus.NO_CONTENT) + @Delete(':id') + async delete(@Param('id') id: string) { + return this.recipeService.delete(id); + } +} diff --git a/api/libs/recipe/src/dto/filter-recipe.dto.ts b/api/libs/recipe/src/dto/filter-recipe.dto.ts new file mode 100644 index 0000000..4a95bc3 --- /dev/null +++ b/api/libs/recipe/src/dto/filter-recipe.dto.ts @@ -0,0 +1,30 @@ +import { IsDate, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class FilterRecipeDto { + @IsString() + @IsNotEmpty() + @IsOptional() + name?: string; + + @IsString() + @IsNotEmpty() + @IsOptional() + description?: string; + @IsString() + @IsNotEmpty() + @IsOptional() + owner?: string; + + @IsString() + @IsNotEmpty() + @IsOptional() + thumbnail?: string; + + @IsDate() + @IsOptional() + created_at?: Date; + + @IsDate() + @IsOptional() + updated_at?: Date; +} diff --git a/api/libs/recipe/src/dto/modify-recipe.dto.ts b/api/libs/recipe/src/dto/modify-recipe.dto.ts new file mode 100644 index 0000000..31f889a --- /dev/null +++ b/api/libs/recipe/src/dto/modify-recipe.dto.ts @@ -0,0 +1,43 @@ +import { ApiHideProperty, OmitType, PartialType } from '@nestjs/swagger'; +import { + ArrayNotEmpty, + IsArray, + IsMongoId, + IsNotEmpty, + IsOptional, + IsString, + IsUrl, +} from 'class-validator'; +import { IngredientRequirement, RecipeStep } from '../entities/recipe.entity'; + +export class CreateRecipeDto { + @IsString() + @IsNotEmpty() + name: string; + + @IsString() + @IsNotEmpty() + description: string; + + @IsMongoId() + @IsString() + @IsOptional() + @ApiHideProperty() + owner: string; + + @ArrayNotEmpty() + @IsArray() + ingredient_requirements: IngredientRequirement[]; + + @ArrayNotEmpty() + @IsArray() + recipe_steps: RecipeStep[]; + + @IsUrl() + @IsString() + thumbnail: string; +} + +export class UpdateRecipeDto extends PartialType( + OmitType(CreateRecipeDto, ['owner'] as const), +) {} diff --git a/api/libs/recipe/src/dto/recipe-response.dto.ts b/api/libs/recipe/src/dto/recipe-response.dto.ts new file mode 100644 index 0000000..7114d93 --- /dev/null +++ b/api/libs/recipe/src/dto/recipe-response.dto.ts @@ -0,0 +1,21 @@ +import { + CreatedResponse, + OkResponse, +} from '@app/common/dto/success-response.dto'; +import { Recipe } from '../entities/recipe.entity'; + +export class CreateRecipeResponseDto extends CreatedResponse { + data: Recipe; +} + +export class FindAllRecipeResponseDto extends OkResponse { + data: Recipe[]; +} + +export class FindOneRecipeResponseDto extends OkResponse { + data: Recipe; +} + +export class UpdateRecipeResponseDto extends OkResponse { + data: Recipe; +} diff --git a/api/libs/recipe/src/entities/recipe.entity.ts b/api/libs/recipe/src/entities/recipe.entity.ts new file mode 100644 index 0000000..972fc08 --- /dev/null +++ b/api/libs/recipe/src/entities/recipe.entity.ts @@ -0,0 +1,58 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; +import { schemaOptions } from '@app/common/utils/schema-option'; + +export type RecipeDocument = HydratedDocument; + +export class IngredientRequirement { + ingredient_id: MongooseSchema.Types.ObjectId; + + name: string; + + amount: string; +} + +export class RecipeStep { + step_count: number; + + description: string; + + image: string; +} + +@Schema(schemaOptions) +export class Recipe { + @Prop({ + required: true, + unique: true, + auto: true, + type: MongooseSchema.Types.ObjectId, + }) + id: MongooseSchema.Types.ObjectId; + + @Prop({ required: true }) + name: string; + + @Prop({ required: true }) + description: string; + + @Prop({ required: true, type: MongooseSchema.Types.ObjectId }) + owner: MongooseSchema.Types.ObjectId; + + @Prop({ required: true, type: Array }) + ingredient_requirements: Array; + + @Prop({ required: true, type: Array }) + recipe_steps: Array; + + @Prop({ required: true }) + thumbnail: string; + + @Prop({ required: false }) + created_at: Date; + + @Prop({ required: false }) + updated_at: Date; +} + +export const RecipeSchema = SchemaFactory.createForClass(Recipe); diff --git a/api/libs/recipe/src/recipe.module.ts b/api/libs/recipe/src/recipe.module.ts new file mode 100644 index 0000000..18a484d --- /dev/null +++ b/api/libs/recipe/src/recipe.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { Recipe, RecipeSchema } from './entities/recipe.entity'; +import { RecipeRepository } from './repositories/recipe.repository'; +import { RecipeService } from './services/recipe.service'; +import { RecipeController } from './controllers/recipe.controller'; + +@Module({ + imports: [ + MongooseModule.forFeatureAsync([ + { + name: Recipe.name, + useFactory: () => { + const schema = RecipeSchema; + return schema; + }, + }, + ]), + ], + controllers: [RecipeController], + providers: [RecipeService, RecipeRepository], + exports: [RecipeService, RecipeRepository], +}) +export class RecipeModule {} diff --git a/api/libs/recipe/src/repositories/recipe.repository.spec.ts b/api/libs/recipe/src/repositories/recipe.repository.spec.ts new file mode 100644 index 0000000..fd16a3f --- /dev/null +++ b/api/libs/recipe/src/repositories/recipe.repository.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@automock/jest'; +import { RecipeRepository } from './recipe.repository'; + +describe('RecipeRepository', () => { + let repository: RecipeRepository; + + beforeEach(async () => { + const { unit, unitRef } = TestBed.create(RecipeRepository).compile(); + + repository = unit; + }); + + it('should be defined', () => { + expect(repository).toBeDefined(); + }); +}); diff --git a/api/libs/recipe/src/repositories/recipe.repository.ts b/api/libs/recipe/src/repositories/recipe.repository.ts new file mode 100644 index 0000000..636f3bb --- /dev/null +++ b/api/libs/recipe/src/repositories/recipe.repository.ts @@ -0,0 +1,22 @@ +import { CommonRepository } from '@app/common/common.repository'; +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { FilterRecipeDto } from '../dto/filter-recipe.dto'; +import { CreateRecipeDto, UpdateRecipeDto } from '../dto/modify-recipe.dto'; +import { Recipe, RecipeDocument } from '../entities/recipe.entity'; + +@Injectable() +export class RecipeRepository extends CommonRepository< + Recipe, + CreateRecipeDto, + UpdateRecipeDto, + FilterRecipeDto +> { + constructor( + @InjectModel(Recipe.name) + private readonly recipeModel: Model, + ) { + super(recipeModel); + } +} diff --git a/api/libs/recipe/src/services/recipe.service.spec.ts b/api/libs/recipe/src/services/recipe.service.spec.ts new file mode 100644 index 0000000..d8ee9c2 --- /dev/null +++ b/api/libs/recipe/src/services/recipe.service.spec.ts @@ -0,0 +1,16 @@ +import { RecipeService } from './recipe.service'; +import { TestBed } from '@automock/jest'; + +describe('RecipeService', () => { + let service: RecipeService; + + beforeEach(async () => { + const { unit, unitRef } = TestBed.create(RecipeService).compile(); + + service = unit; + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/api/libs/recipe/src/services/recipe.service.ts b/api/libs/recipe/src/services/recipe.service.ts new file mode 100644 index 0000000..56653d3 --- /dev/null +++ b/api/libs/recipe/src/services/recipe.service.ts @@ -0,0 +1,18 @@ +import { CommonService } from '@app/common/common.service'; +import { Injectable } from '@nestjs/common'; +import { FilterRecipeDto } from '../dto/filter-recipe.dto'; +import { CreateRecipeDto, UpdateRecipeDto } from '../dto/modify-recipe.dto'; +import { Recipe } from '../entities/recipe.entity'; +import { RecipeRepository } from '../repositories/recipe.repository'; + +@Injectable() +export class RecipeService extends CommonService< + Recipe, + CreateRecipeDto, + UpdateRecipeDto, + FilterRecipeDto +> { + constructor(private readonly recipeRepository: RecipeRepository) { + super(recipeRepository); + } +} diff --git a/api/libs/recipe/tsconfig.lib.json b/api/libs/recipe/tsconfig.lib.json new file mode 100644 index 0000000..f2fe465 --- /dev/null +++ b/api/libs/recipe/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/recipe" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/api/nest-cli.json b/api/nest-cli.json index 59407a0..c21343d 100644 --- a/api/nest-cli.json +++ b/api/nest-cli.json @@ -4,10 +4,6 @@ "monorepo": true, "root": "apps/api", "sourceRoot": "apps/api/src", - "compilerOptions": { - "tsConfigPath": "apps/api/tsconfig.app.json", - "deleteOutDir": true - }, "projects": { "api": { "type": "application", @@ -81,6 +77,15 @@ "compilerOptions": { "tsConfigPath": "libs/image/tsconfig.lib.json" } + }, + "recipe": { + "type": "library", + "root": "libs/recipe", + "entryFile": "index", + "sourceRoot": "libs/recipe/src", + "compilerOptions": { + "tsConfigPath": "libs/recipe/tsconfig.lib.json" + } } } } diff --git a/api/package.json b/api/package.json index 786ab66..e786869 100644 --- a/api/package.json +++ b/api/package.json @@ -99,7 +99,8 @@ "^@app/user(|/.*)$": "/libs/user/src/$1", "^@app/common(|/.*)$": "/libs/common/src/$1", "^@app/ingredient(|/.*)$": "/libs/ingredient/src/$1", - "^@app/image(|/.*)$": "/libs/image/src/$1" + "^@app/image(|/.*)$": "/libs/image/src/$1", + "^@app/recipe(|/.*)$": "/libs/recipe/src/$1" } } -} +} \ No newline at end of file diff --git a/api/tsconfig.json b/api/tsconfig.json index 1947d2b..eaa6c20 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -48,6 +48,12 @@ ], "@app/image/*": [ "libs/image/src/*" + ], + "@app/recipe": [ + "libs/recipe/src" + ], + "@app/recipe/*": [ + "libs/recipe/src/*" ] } }