diff --git a/.github/workflows/be-autodeploy.yml b/.github/workflows/be-autodeploy.yml new file mode 100644 index 0000000..d3c3002 --- /dev/null +++ b/.github/workflows/be-autodeploy.yml @@ -0,0 +1,50 @@ +name: auto deploy + +on: + push: + branches: + - dev + +jobs: + push_to_registry: + name: Push to ncp container registry + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to NCP Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.NCP_CONTAINER_REGISTRY }} + username: ${{ secrets.NCP_ACCESS_KEY }} + password: ${{ secrets.NCP_SECRET_KEY }} + - name: build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ secrets.NCP_CONTAINER_REGISTRY }}/nibobnebob:latest + cache-from: type=registry,ref=${{ secrets.NCP_CONTAINER_REGISTRY }}/nibobnebob:latest + cache-to: type=inline + + pull_from_registry: + name: Connect server ssh and pull from container registry + needs: push_to_registry + runs-on: ubuntu-latest + steps: + - name: connect ssh + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + password: ${{ secrets.DEV_PASSWORD }} + port: ${{ secrets.DEV_PORT }} + script: | + docker pull ${{ secrets.NCP_CONTAINER_REGISTRY }}/nibobnebob + docker stop $(docker ps -a -q) + docker rm $(docker ps -a -q) + docker run -d -p 8000:80 ${{ secrets.NCP_CONTAINER_REGISTRY }}/nibobnebob + docker image prune -f \ No newline at end of file diff --git a/be/Dockerfile b/be/Dockerfile new file mode 100644 index 0000000..2a17d51 --- /dev/null +++ b/be/Dockerfile @@ -0,0 +1,23 @@ +# 베이스 이미지 선택 +FROM node:20 + +# 작업 디렉토리 설정 +WORKDIR /usr/src/app + +# 종속성 파일 복사 (package.json 및 package-lock.json) +COPY package*.json ./ + +# 종속성 설치 +RUN npm install + +# 애플리케이션 소스 코드 복사 +COPY . . + +# TypeScript 컴파일 +RUN npm run build + +# 애플리케이션 실행 포트 지정 +EXPOSE 8000 + +# 애플리케이션 실행 명령어 +CMD ["node", "dist/src/main"] diff --git a/be/docker-compose.yml b/be/docker-compose.yml index 5d8060a..782f89f 100644 --- a/be/docker-compose.yml +++ b/be/docker-compose.yml @@ -11,4 +11,4 @@ services: volumes: - pgdata:/var/lib/postgresql/data volumes: - pgdata: \ No newline at end of file + pgdata: diff --git a/be/dockerignore b/be/dockerignore new file mode 100644 index 0000000..24cdedf --- /dev/null +++ b/be/dockerignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/be/package.json b/be/package.json index 3d81317..02fd800 100644 --- a/be/package.json +++ b/be/package.json @@ -92,4 +92,4 @@ ], "coverageDirectory": "../coverage" } -} +} \ No newline at end of file diff --git a/be/src/restaurant/restaurant.repository.ts b/be/src/restaurant/restaurant.repository.ts index f050e38..370a144 100644 --- a/be/src/restaurant/restaurant.repository.ts +++ b/be/src/restaurant/restaurant.repository.ts @@ -186,6 +186,12 @@ export class RestaurantRepository extends Repository { "current_url.restaurantId = restaurant.id AND current_url.userId = :currentUserId", { currentUserId: tokenInfo.id } ) + .leftJoin( + UserWishRestaurantListEntity, + "user_wish_list", + "user_wish_list.restaurantId = restaurant.id AND user_wish_list.userId = :userId", + { userId: tokenInfo.id } + ) .select([ "restaurant.id", "restaurant.name", @@ -194,6 +200,7 @@ export class RestaurantRepository extends Repository { "restaurant.category", "restaurant.phoneNumber", 'CASE WHEN current_url.user_id IS NOT NULL THEN true ELSE false END AS "isMy"', + `CASE WHEN user_wish_list.userId IS NOT NULL THEN TRUE ELSE FALSE END AS "isWish"`, "restaurant.reviewCnt", ]) .where( @@ -212,6 +219,12 @@ export class RestaurantRepository extends Repository { "user_restaurant_list.restaurantId = restaurant.id AND user_restaurant_list.userId = :userId", { userId: tokenInfo.id } ) + .leftJoin( + UserWishRestaurantListEntity, + "user_wish_list", + "user_wish_list.restaurantId = restaurant.id AND user_wish_list.userId = :userId", + { userId: tokenInfo.id } + ) .select([ "restaurant.id", "restaurant.name", @@ -220,6 +233,7 @@ export class RestaurantRepository extends Repository { "restaurant.category", "restaurant.phoneNumber", 'CASE WHEN user_restaurant_list.userId IS NOT NULL THEN TRUE ELSE FALSE END AS "isMy"', + `CASE WHEN user_wish_list.userId IS NOT NULL THEN TRUE ELSE FALSE END AS "isWish"`, "restaurant.reviewCnt", ]) .where("restaurant.id = :restaurantId", { restaurantId }) diff --git a/be/src/restaurant/restaurant.service.ts b/be/src/restaurant/restaurant.service.ts index 54ca845..cda63ac 100644 --- a/be/src/restaurant/restaurant.service.ts +++ b/be/src/restaurant/restaurant.service.ts @@ -27,7 +27,7 @@ export class RestaurantService implements OnModuleInit { private restaurantRepository: RestaurantRepository, private userRepository: UserRepository, private reviewRepository: ReviewRepository - ) {} + ) { } async searchRestaurant(searchInfoDto: SearchInfoDto, tokenInfo: TokenInfo) { const restaurants = await this.restaurantRepository.searchRestarant( @@ -55,8 +55,9 @@ export class RestaurantService implements OnModuleInit { tokenInfo ); - const [reviews, reviewCount] = await this.reviewRepository + const reviews = await this.reviewRepository .createQueryBuilder("review") + .leftJoinAndSelect("review.user", "user") .select([ "review.id", "review.isCarVisit", @@ -66,14 +67,17 @@ export class RestaurantService implements OnModuleInit { "review.service", "review.restroomCleanliness", "review.overallExperience", + "user.nickName as reviewer", + "review.createdAt" ]) .where("review.restaurant_id = :restaurantId", { restaurantId: restaurant.restaurant_id, }) - .getManyAndCount(); + .getRawMany(); + restaurant.restaurant_reviewCnt = reviews.length; restaurant.reviews = reviews.slice(0, 3); - restaurant.restaurant_reviewCnt = reviewCount; + return restaurant; } @@ -132,9 +136,8 @@ export class RestaurantService implements OnModuleInit { "+proj=tmerc +lat_0=38 +lon_0=127.0028902777778 +k=1 +x_0=200000 +y_0=500000 +ellps=bessel +units=m +no_defs +towgs84=-115.80,474.99,674.11,1.16,-2.31,-1.63,6.43"; const wgs84 = "EPSG:4326"; - const apiUrl = `http://openapi.seoul.go.kr:8088/${key}/json/LOCALDATA_072404/${startPage}/${ - startPage + 999 - }/`; + const apiUrl = `http://openapi.seoul.go.kr:8088/${key}/json/LOCALDATA_072404/${startPage}/${startPage + 999 + }/`; const response = axios.get(apiUrl); diff --git a/be/src/review/dto/reviewInfo.dto.ts b/be/src/review/dto/reviewInfo.dto.ts index 5bad4e8..ee47c7b 100644 --- a/be/src/review/dto/reviewInfo.dto.ts +++ b/be/src/review/dto/reviewInfo.dto.ts @@ -7,6 +7,8 @@ import { MaxLength, IsOptional, MinLength, + Max, + Min, } from "class-validator"; export class ReviewInfoDto { @@ -24,7 +26,8 @@ export class ReviewInfoDto { }) @IsInt() @IsOptional() - @MaxLength(1) + @Min(0) + @Max(4) transportationAccessibility: number | null; @ApiProperty({ @@ -33,19 +36,22 @@ export class ReviewInfoDto { }) @IsInt() @IsOptional() - @MaxLength(1) + @Min(0) + @Max(4) parkingArea: number | null; @ApiProperty({ example: "0", description: "The taste of the food" }) @IsInt() @IsNotEmpty() - @MaxLength(1) + @Min(0) + @Max(4) taste: number; @ApiProperty({ example: "0", description: "The service of the restaurant" }) @IsInt() @IsNotEmpty() - @MaxLength(1) + @Min(0) + @Max(4) service: number; @ApiProperty({ @@ -54,7 +60,8 @@ export class ReviewInfoDto { }) @IsInt() @IsNotEmpty() - @MaxLength(1) + @Min(0) + @Max(4) restroomCleanliness: number; @ApiProperty({ diff --git a/be/src/user/entities/user.restaurantlist.entity.ts b/be/src/user/entities/user.restaurantlist.entity.ts index 913e81b..82d494a 100644 --- a/be/src/user/entities/user.restaurantlist.entity.ts +++ b/be/src/user/entities/user.restaurantlist.entity.ts @@ -5,10 +5,11 @@ import { ManyToOne, JoinColumn, PrimaryColumn, + DeleteDateColumn, } from "typeorm"; import { User } from "./user.entity"; -import { RestaurantInfoEntity } from "src/restaurant/entities/restaurant.entity"; -import { ReviewInfoEntity } from "src/review/entities/review.entity"; +import { RestaurantInfoEntity } from "../../restaurant/entities/restaurant.entity"; +import { ReviewInfoEntity } from "../../review/entities/review.entity"; @Entity("user_restaurant_lists") export class UserRestaurantListEntity { @@ -21,7 +22,7 @@ export class UserRestaurantListEntity { @CreateDateColumn({ name: "created_at", type: "timestamp" }) createdAt: Date; - @Column({ name: "deleted_at", type: "timestamp", nullable: true }) + @DeleteDateColumn({ name: "deleted_at", type: "timestamp", nullable: true }) deletedAt: Date | null; @ManyToOne(() => User) diff --git a/be/src/user/entities/user.wishrestaurantlist.entity.ts b/be/src/user/entities/user.wishrestaurantlist.entity.ts index 5b5bcba..6709f0e 100644 --- a/be/src/user/entities/user.wishrestaurantlist.entity.ts +++ b/be/src/user/entities/user.wishrestaurantlist.entity.ts @@ -5,9 +5,10 @@ import { ManyToOne, JoinColumn, PrimaryColumn, + DeleteDateColumn, } from "typeorm"; import { User } from "./user.entity"; -import { RestaurantInfoEntity } from "src/restaurant/entities/restaurant.entity"; +import { RestaurantInfoEntity } from "../../restaurant/entities/restaurant.entity"; @Entity("user_wishrestaurant_lists") export class UserWishRestaurantListEntity { @@ -20,7 +21,7 @@ export class UserWishRestaurantListEntity { @CreateDateColumn({ name: "created_at", type: "timestamp" }) createdAt: Date; - @Column({ name: "deleted_at", type: "timestamp", nullable: true }) + @DeleteDateColumn({ name: "deleted_at", type: "timestamp", nullable: true }) deletedAt: Date | null; @ManyToOne(() => User) diff --git a/be/src/user/user.controller.ts b/be/src/user/user.controller.ts index 5627674..f7a37d4 100644 --- a/be/src/user/user.controller.ts +++ b/be/src/user/user.controller.ts @@ -26,10 +26,11 @@ import { AuthGuard } from "@nestjs/passport"; import { SearchInfoDto } from "../restaurant/dto/seachInfo.dto"; import { LocationDto } from "src/restaurant/dto/location.dto"; import { ReviewInfoDto } from "src/review/dto/reviewInfo.dto"; +import { ParseArrayPipe } from "../utils/parsearraypipe"; @Controller("user") export class UserController { - constructor(private userService: UserService) {} + constructor(private userService: UserService) { } @ApiTags("Mypage") @Get() @@ -79,6 +80,13 @@ export class UserController { @ApiTags("Follow/Following") @Get("/autocomplete/:partialUsername") @UseGuards(AuthGuard("jwt")) + @ApiQuery({ + name: 'region', + required: false, + type: String, + isArray: true, + description: '필터링할 지역 목록을 쉼표(,)로 구분하여 제공' + }) @ApiBearerAuth() @ApiOperation({ summary: "다른 유저 검색 자동완성" }) @ApiResponse({ status: 200, description: "다른 유저 검색 자동완성 완성" }) @@ -86,9 +94,10 @@ export class UserController { @ApiResponse({ status: 400, description: "부적절한 요청" }) async searchTargetUser( @GetUser() tokenInfo: TokenInfo, - @Param("partialUsername") partialUsername: string + @Param("partialUsername") partialUsername: string, + @Query("region", ParseArrayPipe) region: string[] ) { - return await this.userService.searchTargetUser(tokenInfo, partialUsername); + return await this.userService.searchTargetUser(tokenInfo, partialUsername, region); } @ApiTags("Signup", "Mypage") @@ -252,6 +261,7 @@ export class UserController { @ApiResponse({ status: 200, description: "맛집리스트 등록 성공" }) @ApiResponse({ status: 401, description: "인증 실패" }) @ApiResponse({ status: 400, description: "부적절한 요청" }) + @UsePipes(new ValidationPipe()) async addRestaurantToNebob( @Body() reviewInfoDto: ReviewInfoDto, @GetUser() tokenInfo: TokenInfo, diff --git a/be/src/user/user.repository.ts b/be/src/user/user.repository.ts index 10f0f74..47138c3 100644 --- a/be/src/user/user.repository.ts +++ b/be/src/user/user.repository.ts @@ -55,7 +55,7 @@ export class UserRepository extends Repository { select: ["nickName", "region"], where: { id: In(targetInfoIds) }, }); - return { userInfo: userInfo }; + return userInfo; } async getMypageUserDetailInfo(id: number) { const userInfo = await this.findOne({ diff --git a/be/src/user/user.service.spec.ts b/be/src/user/user.service.spec.ts index 09e87e7..5e61a2c 100644 --- a/be/src/user/user.service.spec.ts +++ b/be/src/user/user.service.spec.ts @@ -7,48 +7,53 @@ import { newDb } from "pg-mem"; import { DataSource, TypeORMError } from "typeorm"; import { User } from "./entities/user.entity"; import { TypeOrmModule } from "@nestjs/typeorm"; +import { FollowEntity } from "./entities/user.followList.entity"; +import { RestaurantInfoEntity } from "../restaurant/entities/restaurant.entity"; +import { UserRestaurantListEntity } from "./entities/user.restaurantlist.entity"; +import { UserWishRestaurantListEntity } from "./entities/user.wishrestaurantlist.entity"; +import { ReviewInfoEntity } from "../review/entities/review.entity"; +import { UserFollowListRepository } from "./user.followList.repository"; +import { UserRestaurantListRepository } from "./user.restaurantList.repository"; +import { UserWishRestaurantListRepository } from "./user.wishrestaurantList.repository"; +import { RestaurantRepository } from "../restaurant/restaurant.repository"; +import { ReviewRepository } from "../review/review.repository"; describe("UserService", () => { let userService: UserService; let dataSource: DataSource; - beforeAll(async () => { - const db = newDb({ autoCreateForeignKeyIndices: true }); - - db.public.registerFunction({ - name: "current_database", - implementation: () => "test_database", - }); - - db.public.registerFunction({ - name: "version", - implementation: () => - "PostgreSQL 12.16, compiled by Visual C++ build 1914, 64-bit", - }); - - dataSource = await db.adapters.createTypeormDataSource({ - type: "postgres", - entities: [User], - }); - - await dataSource.initialize(); - await dataSource.synchronize(); + beforeEach(async () => { + const userRepository = dataSource.getRepository(User); + await userRepository.query(`DELETE FROM public.user;`); + }); + beforeAll(async () => { const testModule = await Test.createTestingModule({ imports: [ AuthModule, - TypeOrmModule.forRoot(), - TypeOrmModule.forFeature([User]), + TypeOrmModule.forRoot({ + type: 'postgres', + host: 'localhost', + port: 5433, + username: 'user', + password: 'password', + database: 'testdb', + entities: [User, FollowEntity, RestaurantInfoEntity, UserRestaurantListEntity, UserWishRestaurantListEntity, ReviewInfoEntity], + synchronize: true, + }), + TypeOrmModule.forFeature([User, RestaurantRepository, UserRepository, UserRestaurantListRepository, UserFollowListRepository, UserWishRestaurantListRepository, ReviewRepository]), ], - providers: [UserService, UserRepository], - }) - .overrideProvider(DataSource) - .useValue(dataSource) - .compile(); + providers: [UserService], + }).compile(); userService = testModule.get(UserService); + dataSource = testModule.get(DataSource); }); - it("should create a new user and return the user data", async () => { + afterAll(async () => { + await dataSource.destroy(); + }); + + it("회원가입", async () => { const userInfoDto: UserInfoDto = { email: "test@email.com", password: "1234", @@ -68,7 +73,7 @@ describe("UserService", () => { where: { email: "test@email.com" }, }); - expect(foundUser).toEqual( + expect(foundUser).toEqual(await expect.objectContaining({ nickName: "hi", region: "인천", @@ -78,3 +83,216 @@ describe("UserService", () => { ); }); }); + +describe("마이페이지", () => { + let userService: UserService; + let dataSource: DataSource; + beforeEach(async () => { + const userRepository = dataSource.getRepository(User); + await userRepository.query(`DELETE FROM public.user;`); + }); + + + beforeAll(async () => { + const testModule = await Test.createTestingModule({ + imports: [ + AuthModule, + TypeOrmModule.forRoot({ + type: 'postgres', + host: 'localhost', + port: 5433, + username: 'user', + password: 'password', + database: 'testdb', + entities: [User, FollowEntity, RestaurantInfoEntity, UserRestaurantListEntity, UserWishRestaurantListEntity, ReviewInfoEntity], + synchronize: true, + }), + TypeOrmModule.forFeature([User, RestaurantRepository, UserRepository, UserRestaurantListRepository, UserFollowListRepository, UserWishRestaurantListRepository, ReviewRepository]), + ], + providers: [UserService], + }).compile(); + + userService = testModule.get(UserService); + dataSource = testModule.get(DataSource); + }); + + afterAll(async () => { + await dataSource.destroy(); + }); + it("닉네임 중복 확인 요청", async () => { + + const firstResult = await userService.getNickNameAvailability("hi"); + + expect(firstResult).toEqual(await + expect.objectContaining({ + isexist: false + }) + ); + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + await userRepository.save(userInfoDto); + + const result = await userService.getNickNameAvailability("hi"); + + expect(result).toEqual(await + expect.objectContaining({ + isexist: true + }) + ); + }); + + it("이메일 중복 확인 요청", async () => { + + const firstResult = await userService.getEmailAvailability("test@email.com"); + + expect(firstResult).toEqual(await + expect.objectContaining({ + isexist: false + }) + ); + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + await userRepository.save(userInfoDto); + + const result = await userService.getEmailAvailability("test@email.com"); + + expect(result).toEqual(await + expect.objectContaining({ + isexist: true + }) + ); + }); + + it("마이페이지 정보 요청", async () => { + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + const id = await userRepository.save(userInfoDto); + + const mypageInfo = await userService.getMypageUserInfo(id) + + expect(mypageInfo).toEqual(await + expect.objectContaining({ + "userInfo": { + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true, + }, + }) + ); + }); + it("마이페이지 수정 페이지 정보 요청", async () => { + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + const id = await userRepository.save(userInfoDto); + + const mypageInfo = await userService.getMypageUserDetailInfo(id) + + expect(mypageInfo).toEqual(await + expect.objectContaining({ + "userInfo": { + email: "test@email.com", + nickName: "hi", + provider: " ", + region: "인천", + birthdate: "1999/10/13", + isMale: true, + }, + }) + ); + }); + + it("유저 회원탈퇴", async () => { + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + const id = await userRepository.save(userInfoDto); + + await userService.deleteUserAccount(id) + + const result = await userRepository.findOne({ where: { id: id["id"] } }); + + expect(result).toEqual(await + expect.objectContaining({ + + }) + ); + }); + it("유저 정보 업데이트", async () => { + const userInfoDto: UserInfoDto = { + email: "test@email.com", + password: "1234", + provider: " ", + nickName: "hi", + region: "인천", + birthdate: "1999/10/13", + isMale: true + }; + const userRepository = dataSource.getRepository(User); + const id = await userRepository.save(userInfoDto); + + const userUpdateInfoDto: UserInfoDto = { + email: "testSuccess@email.com", + password: "4321", + provider: " ", + nickName: "Good", + region: "서울", + birthdate: "2000/01/03", + isMale: false + } + + await userService.updateMypageUserInfo(id, userUpdateInfoDto) + + const result = await userRepository.findOne({ where: { id: id["id"] } }); + expect(result).toEqual(await + expect.objectContaining({ + email: "testSuccess@email.com", + password: "4321", + provider: " ", + nickName: "Good", + region: "서울", + birthdate: "2000/01/03", + isMale: false + }) + ); + }); +}); diff --git a/be/src/user/user.service.ts b/be/src/user/user.service.ts index 88455b0..d823193 100644 --- a/be/src/user/user.service.ts +++ b/be/src/user/user.service.ts @@ -14,6 +14,8 @@ import { ReviewRepository } from "src/review/review.repository"; import { UserWishRestaurantListRepository } from "./user.wishrestaurantList.repository"; import { AwsService } from "src/aws/aws.service"; import { v4 } from "uuid"; +import { User } from "./entities/user.entity"; +import { RestaurantInfoEntity } from "src/restaurant/entities/restaurant.entity"; @Injectable() export class UserService { @@ -169,20 +171,24 @@ export class UserService { isFollow: false, })); } - async searchTargetUser(tokenInfo: TokenInfo, nickName: string) { + async searchTargetUser(tokenInfo: TokenInfo, nickName: string, region: string[]) { + const whereCondition: any = { + nickName: Like(`%${nickName}%`), + id: Not(Equal(tokenInfo.id)), + }; + if (region) { + whereCondition.region = In(region); + } const users = await this.usersRepository.find({ select: ["id"], - where: { - nickName: Like(`%${nickName}%`), - id: Not(Equal(tokenInfo.id)), - }, + where: whereCondition, take: 20, }); if (users.length) { const userIds = users.map((user) => user.id); const result = await this.usersRepository.getUsersInfo(userIds); - for (let i in result.userInfo) { - result.userInfo[i]["isFollow"] = + for (let i in result) { + result[i]["isFollow"] = (await this.userFollowListRepositoy.getFollowState( tokenInfo.id, userIds[i] @@ -192,7 +198,7 @@ export class UserService { } return result; } - return null; + return []; } async followUser(tokenInfo: TokenInfo, nickName: string) { @@ -232,6 +238,13 @@ export class UserService { restaurantId: number ) { const reviewEntity = this.reviewRepository.create(reviewInfoDto); + const userEntity = new User(); + userEntity.id = tokenInfo["id"]; + reviewEntity.user = userEntity; + + const restaurantEntity = new RestaurantInfoEntity(); + restaurantEntity.id = restaurantId; + reviewEntity.restaurant = restaurantEntity; try { await this.reviewRepository.save(reviewEntity); await this.userRestaurantListRepository.addRestaurantToNebob( diff --git a/be/src/utils/parsearraypipe.ts b/be/src/utils/parsearraypipe.ts new file mode 100644 index 0000000..aa7cf84 --- /dev/null +++ b/be/src/utils/parsearraypipe.ts @@ -0,0 +1,11 @@ +import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; + +@Injectable() +export class ParseArrayPipe implements PipeTransform { + transform(value: any, metadata: ArgumentMetadata) { + if (metadata.type === 'query' && typeof value === 'string') { + return value.split(','); + } + return value; + } +}