Skip to content

Commit

Permalink
[Feat]: open recipe create endpoint (#110)
Browse files Browse the repository at this point in the history
* open create recipe endpoint

* fix incr view count logic and api docs

* add test and fix wrong logic & entity props type
  • Loading branch information
Istiopaxx authored Dec 6, 2023
1 parent 958812e commit 18f0a0b
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 72 deletions.
16 changes: 3 additions & 13 deletions api/libs/recipe/src/controllers/recipe.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import {
CreateRecipeDto,
CreateMongoRecipeDto,
UpdateRecipeDto,
} from '../dto/recipe/modify-recipe.dto';
import {
Expand Down Expand Up @@ -55,11 +55,7 @@ export class RecipeController {
@Auth()
@ApiPostCreated(CreateRecipeResponseDto)
@Post()
async create(
@Body() createRecipeDto: CreateRecipeDto,
@ReqUser() user: User,
) {
createRecipeDto.owner_id = user.id;
async create(@Body() createRecipeDto: CreateMongoRecipeDto) {
return this.recipeService.create(createRecipeDto);
}

Expand All @@ -74,13 +70,7 @@ export class RecipeController {
@Auth()
@ApiGet(FindRecipesResponseDto)
@Get()
async findAll(
@ReqUser() user: User,
@Query() filterRecipeDto: FilterRecipeDto,
) {
// const filterRecipeDto: FilterRecipeDto = queryBuilder({
// user_id: user.id.toString(),
// });
async findAll(@Query() filterRecipeDto: FilterRecipeDto) {
return this.recipeService.findAll(filterRecipeDto);
}

Expand Down
5 changes: 5 additions & 0 deletions api/libs/recipe/src/dto/recipe/filter-recipe.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import {
IsDate,
IsEnum,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Expand Down Expand Up @@ -45,6 +46,10 @@ export class FilterRecipeDto extends PagenationDto {
@IsDate()
@IsOptional()
updated_at?: Date;

@IsInt()
@IsOptional()
mysql_id?: number;
}

export enum TextSearchSortBy {
Expand Down
40 changes: 8 additions & 32 deletions api/libs/recipe/src/dto/recipe/modify-recipe.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,43 +69,14 @@ export class RecipeStepDto {
}

export class CreateMongoRecipeDto {
constructor(
name: string,
description: string,
owner: string,
ingredient_requirements: IngredientRequirementDto[],
recipe_steps: RecipeStepDto[],
thumbnail: string,
recipe_raw_text: string,
origin_url: string,
) {
this.name = name;
this.description = description;
this.owner = owner;
this.ingredient_requirements = ingredient_requirements.map(
(item) =>
new IngredientRequirementDto(
item.ingredient_id,
item.name,
item.amount,
),
);
this.recipe_steps = recipe_steps.map(
(item) =>
new RecipeStepDto(item.description, item.images, item.ingredients),
);
this.thumbnail = thumbnail;
this.recipe_raw_text = recipe_raw_text;
this.origin_url = origin_url;
}

@IsString()
@IsNotEmpty()
name: string;

@IsInt()
@Min(1)
mysql_id: number;
@IsOptional()
mysql_id?: number;

@IsString()
@IsNotEmpty()
Expand Down Expand Up @@ -144,8 +115,9 @@ export class CreateRecipeDto {
@IsNotEmpty()
name: string;

@IsOptional()
@IsMongoId()
mongo_id: string;
mongo_id?: string;

@IsOptional()
@IsInt()
Expand Down Expand Up @@ -174,6 +146,10 @@ export class UpdateRecipeDto extends PartialType(
@IsOptional()
@IsInt()
view_count?: number;

@IsOptional()
@IsMongoId()
mongo_id?: string;
}

export class UpdateMongoRecipeDto extends PartialType(
Expand Down
2 changes: 1 addition & 1 deletion api/libs/recipe/src/dto/recipe/recipe-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class FindTopViewdResponseDto extends OkResponse {
}

export class FindRecentViewedResponseDto extends OkResponse {
data: RecipeListViewResponseDto[];
data: RecipesResponseDto;
}

export class FindOneRecipeResponseDto extends OkResponse {
Expand Down
16 changes: 9 additions & 7 deletions api/libs/recipe/src/entities/mongo/mongo.recipe.entity.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument, Schema as MongooseSchema } from 'mongoose';
import { HydratedDocument, Types } from 'mongoose';
import { schemaOptions } from '@app/common/utils/schema-option';

export type RecipeDocument = HydratedDocument<Recipe>;

export class IngredientRequirement {
ingredient_id: MongooseSchema.Types.ObjectId;
ingredient_id: Types.ObjectId;

name: string;

Expand All @@ -26,11 +26,11 @@ export class Recipe {
required: true,
unique: true,
auto: true,
type: MongooseSchema.Types.ObjectId,
type: Types.ObjectId,
})
id: MongooseSchema.Types.ObjectId;
id: Types.ObjectId;

@Prop({ required: true, unique: true, index: true })
@Prop({ required: false, index: true })
mysql_id: number;

@Prop({ required: true })
Expand All @@ -39,8 +39,8 @@ export class Recipe {
@Prop({ required: true })
description: string;

@Prop({ required: false, type: MongooseSchema.Types.ObjectId })
owner: MongooseSchema.Types.ObjectId;
@Prop({ required: false, type: Types.ObjectId })
owner: Types.ObjectId;

@Prop({ required: true, type: Array<IngredientRequirement> })
ingredient_requirements: Array<IngredientRequirement>;
Expand Down Expand Up @@ -68,3 +68,5 @@ export class Recipe {
}

export const RecipeSchema = SchemaFactory.createForClass(Recipe);

RecipeSchema.index({ '$**': 'text' });
68 changes: 62 additions & 6 deletions api/libs/recipe/src/repositories/recipe/mongo.recipe.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class MongoRecipeRepository {
);
}

@Logable()
async findAllByFullTextSearch(
textSearchRecipeDto: TextSearchRecipeDto,
): Promise<RecipesAndCountDto> {
Expand Down Expand Up @@ -152,15 +153,66 @@ export class MongoRecipeRepository {
);
}

@Logable()
async findAllByFullTextSearch2(
textSearchRecipeDto: TextSearchRecipeDto,
): Promise<RecipesAndCountDto> {
const { page, limit } = textSearchRecipeDto;
const aggrpipe = this.recipeModel
.aggregate()
.match({
$text: {
$search: textSearchRecipeDto.searchQuery,
},
})
.sort({
score: { $meta: 'textScore' },
})
.project({
score: { $meta: 'textScore' },
_id: 0,
__v: 0,
recipe_raw_text: 0,
origin_url: 0,
recipe_steps: 0,
ingredient_requirements: 0,
})
.facet({
recipes: [
{
$addFields: {
mongo_id: '$id',
id: '$mysql_id',
},
},
{
$project: {
mysql_id: 0,
},
},
{ $skip: (page - 1) * limit },
{ $limit: limit },
],
count: [{ $count: 'count' }],
})
.pipeline();

const ret = await this.recipeModel.aggregate(aggrpipe).exec();
return new RecipesAndCountDto(
ret[0].recipes,
ret[0].count.length > 0 ? ret[0].count[0].count : 0,
);
}

@Logable()
@Cacheable({
ttl: 5 * 60 * 1000,
keyGenerator: (id: string) => `recipe-mongo:${id}`,
keyGenerator: (mySqlId: number) => `recipe:id:mysql-${mySqlId}`,
})
async findOneByMysqlId(id: number): Promise<RecipeDto> {
async findOneByMysqlId(mySqlId: number): Promise<RecipeDto> {
return (
await this.recipeModel
.findOne({ mysql_id: id })
.findOne({ mysql_id: mySqlId })
.select({
_id: 0,
__v: 0,
Expand All @@ -174,7 +226,7 @@ export class MongoRecipeRepository {
@Logable()
@Cacheable({
ttl: 5 * 60 * 1000,
keyGenerator: (id: string) => `recipe-mongo:${id}`,
keyGenerator: (id: string) => `recipe:id:mongo-${id}`,
})
async findOne(id: string): Promise<RecipeDto> {
return (
Expand Down Expand Up @@ -224,10 +276,14 @@ export class MongoRecipeRepository {
).map((recipe) => recipe.toObject());
}

async increaseViewCount(id: string): Promise<Recipe> {
async increaseViewCountByMySqlId(mySqlId: number): Promise<Recipe> {
return (
await this.recipeModel
.findOneAndUpdate({ id }, { $inc: { view_count: 1 } }, { new: true })
.findOneAndUpdate(
{ mysql_id: mySqlId },
{ $inc: { view_count: 1 } },
{ new: true },
)
.exec()
)?.toObject();
}
Expand Down
6 changes: 6 additions & 0 deletions api/libs/recipe/src/repositories/recipe/recipe.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ export class RecipeRepository
return new RecipesAndCountDto(recipes, count);
}

async findOneByMongoId(mongoId: string): Promise<Recipe> {
return this.prisma.recipe.findUnique({
where: { mongo_id: mongoId },
});
}

async findAllByFullTextSearch(
_textSearchRecipeDto: TextSearchRecipeDto,
): Promise<RecipesAndCountDto> {
Expand Down
29 changes: 25 additions & 4 deletions api/libs/recipe/src/services/recipe.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
TextSearchRecipeDto,
} from '../dto/recipe/filter-recipe.dto';
import {
CreateRecipeDto,
CreateMongoRecipeDto,
UpdateRecipeDto,
} from '../dto/recipe/modify-recipe.dto';
import { MongoRecipeRepository } from '../repositories/recipe/mongo.recipe.repository';
Expand All @@ -18,7 +18,9 @@ import { RecipeViewLogRepository } from '../repositories/recipe-view-log/recipe-
import { RecipeViewLog } from '../entities/recipe-view-log.entity';
import { User } from '@app/user/entities/user.entity';
import { Recipe } from '../entities/recipe.entity';
import { Recipe as MongoRecipe } from '../entities/mongo/mongo.recipe.entity';
import { RecipeViewerIdentifier } from '../dto/recipe-view-log/recipe-viewer-identifier';
import { Types } from 'mongoose';

describe('RecipeService', () => {
let service: RecipeService;
Expand Down Expand Up @@ -75,13 +77,25 @@ describe('RecipeService', () => {

describe('create', () => {
it('should create a new recipe', async () => {
const createRecipeDto = {} as CreateRecipeDto;
const recipe = new Recipe();
const createRecipeDto = {} as CreateMongoRecipeDto;
const mongoId = new Types.ObjectId();
const recipe: Recipe = { ...new Recipe(), id: 1 },
mongoRecipe: MongoRecipe = {
...new MongoRecipe(),
id: mongoId,
};
recipeRepository.create.mockResolvedValue(recipe);
mongoRecipeRepository.create.mockResolvedValue(mongoRecipe);

const result = await service.create(createRecipeDto);

expect(recipeRepository.create).toHaveBeenCalledWith(createRecipeDto);
expect(mongoRecipeRepository.create).toHaveBeenCalledWith(
createRecipeDto,
);
expect(recipeRepository.create).toHaveBeenCalledWith({
...createRecipeDto,
mongo_id: mongoId.toString(),
});
expect(result).toEqual(recipe);
});
});
Expand Down Expand Up @@ -213,6 +227,9 @@ describe('RecipeService', () => {
const recipe = new RecipeDto();
mongoRecipeRepository.findOneByMysqlId.mockResolvedValue(recipe);
recipeRepository.increaseViewCount.mockResolvedValue(new Recipe());
mongoRecipeRepository.increaseViewCountByMySqlId.mockResolvedValue(
new MongoRecipe(),
);
recipeViewLogRepository.create.mockResolvedValue(new RecipeViewLog());

const result = await service.findOne(1, {
Expand Down Expand Up @@ -303,6 +320,9 @@ describe('RecipeService', () => {
describe('viewRecipe', () => {
it('should return true', async () => {
recipeRepository.increaseViewCount.mockResolvedValue(new Recipe());
mongoRecipeRepository.increaseViewCountByMySqlId.mockResolvedValue(
new MongoRecipe(),
);
recipeViewLogRepository.create.mockResolvedValue(new RecipeViewLog());

const result = await service.viewRecipe(1, {
Expand All @@ -324,6 +344,7 @@ describe('RecipeService', () => {

it('should throw NotFoundException', async () => {
recipeRepository.increaseViewCount.mockResolvedValue(null);
mongoRecipeRepository.increaseViewCountByMySqlId.mockResolvedValue(null);

await expect(
service.viewRecipe(1, {
Expand Down
Loading

0 comments on commit 18f0a0b

Please sign in to comment.