Skip to content

Commit

Permalink
Merge pull request #212 from boostcampwm2023/feature/be-review-final
Browse files Browse the repository at this point in the history
리뷰 평가 및 정렬, 페이징 기능 구현
  • Loading branch information
LeeTH916 authored Dec 5, 2023
2 parents 13d0d9d + f2cdc73 commit 82e9999
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 4 deletions.
4 changes: 3 additions & 1 deletion be/src/restaurant/restaurant.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class RestaurantService implements OnModuleInit {
const reviews = await this.reviewRepository
.createQueryBuilder("review")
.leftJoinAndSelect("review.user", "user")
.leftJoin("review.reviewLikes", "reviewLike", "reviewLike.userId = :userId", { userId: tokenInfo.id })
.select([
"review.id",
"review.isCarVisit",
Expand All @@ -71,7 +72,8 @@ export class RestaurantService implements OnModuleInit {
"review.overallExperience",
"user.nickName as reviewer",
"review.createdAt",
"review.reviewImage"
"review.reviewImage",
"reviewLike.isLike as isLike"
])
.where("review.restaurant_id = :restaurantId", {
restaurantId: restaurant.restaurant_id,
Expand Down
5 changes: 5 additions & 0 deletions be/src/review/entities/review.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
ManyToOne,
JoinColumn,
OneToOne,
OneToMany,
} from "typeorm";
import { User } from "src/user/entities/user.entity";
import { RestaurantInfoEntity } from "src/restaurant/entities/restaurant.entity";
import { UserRestaurantListEntity } from "src/user/entities/user.restaurantlist.entity";
import { ReviewLikeEntity } from "./review.like.entity";

@Entity("review")
export class ReviewInfoEntity {
Expand Down Expand Up @@ -43,6 +45,9 @@ export class ReviewInfoEntity {
@CreateDateColumn({ name: "created_at" })
createdAt: Date;

@OneToMany(() => ReviewLikeEntity, reviewLike => reviewLike.review)
reviewLikes: ReviewLikeEntity[];

@ManyToOne(() => User)
@JoinColumn({ name: "user_id" })
user: User;
Expand Down
37 changes: 37 additions & 0 deletions be/src/review/entities/review.like.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
Entity,
PrimaryColumn,
Column,
CreateDateColumn,
ManyToOne,
JoinColumn,
DeleteDateColumn,
} from "typeorm";
import { User } from "src/user/entities/user.entity";
import { ReviewInfoEntity } from "./review.entity";

@Entity("reviewLike")
export class ReviewLikeEntity {
@PrimaryColumn({ name: "review_id" })
reviewId: number;

@PrimaryColumn({ name: "user_id" })
userId: number;

@Column({ name: "is_like" })
isLike: boolean;

@CreateDateColumn({ name: "created_at" })
createdAt: Date;

@DeleteDateColumn({ name: "deleted_at", nullable: true, type: "timestamp" })
deletedAt: Date | null;

@ManyToOne(() => ReviewInfoEntity)
@JoinColumn({ name: "review_id", referencedColumnName: "id" })
review: ReviewInfoEntity;

@ManyToOne(() => User)
@JoinColumn({ name: "user_id", referencedColumnName: "id" })
user: User;
}
18 changes: 18 additions & 0 deletions be/src/review/review.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ReviewController } from './review.controller';

describe('ReviewController', () => {
let controller: ReviewController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ReviewController],
}).compile();

controller = module.get<ReviewController>(ReviewController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
67 changes: 67 additions & 0 deletions be/src/review/review.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Controller, Get, Param, Post, Query, UseGuards, UsePipes, ValidationPipe } from '@nestjs/common';
import { ReviewService } from './review.service';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { GetUser, TokenInfo } from 'src/user/user.decorator';
import { SortInfoDto } from 'src/utils/sortInfo.dto';

@ApiTags("Review")
@Controller('review')
export class ReviewController {
constructor(private reviewService: ReviewService) { }

@Get("/:restaurantId")
@UseGuards(AuthGuard("jwt"))
@ApiBearerAuth()
@ApiQuery({ name: 'sort', required: false, description: '정렬 기준' })
@ApiQuery({ name: 'page', required: false, description: '페이지 번호' })
@ApiQuery({ name: 'limit', required: false, description: '페이지 당 항목 수' })

@ApiOperation({ summary: "리뷰 정렬 요청" })
@ApiResponse({ status: 200, description: "리뷰 정렬 요청 성공" })
@ApiResponse({ status: 401, description: "인증 실패" })
@ApiResponse({ status: 400, description: "부적절한 요청" })
@UsePipes(new ValidationPipe())
async getSortedReviews(
@GetUser() tokenInfo: TokenInfo,
@Param('restaurantId') restaurantId: string,
@Query() getSortedReviewsDto: SortInfoDto
) {
const restaurantNumber = parseInt(restaurantId, 10);
return await this.reviewService.getSortedReviews(tokenInfo, restaurantNumber, getSortedReviewsDto);
}

@Post("/:reviewId/like")
@UseGuards(AuthGuard("jwt"))
@ApiBearerAuth()
@ApiOperation({ summary: "리뷰 좋아요 요청" })
@ApiResponse({ status: 200, description: "리뷰 좋아요 요청 성공" })
@ApiResponse({ status: 401, description: "인증 실패" })
@ApiResponse({ status: 400, description: "부적절한 요청" })
async reviewLike(
@GetUser() tokenInfo: TokenInfo,
@Param("reviewId") reviewid: number
) {
return await this.reviewService.reviewLike(
tokenInfo,
reviewid
);
}

@Post("/:reviewId/unlike")
@UseGuards(AuthGuard("jwt"))
@ApiBearerAuth()
@ApiOperation({ summary: "리뷰 싫어요 요청" })
@ApiResponse({ status: 200, description: "리뷰 싫어요 요청 성공" })
@ApiResponse({ status: 401, description: "인증 실패" })
@ApiResponse({ status: 400, description: "부적절한 요청" })
async reviewUnLike(
@GetUser() tokenInfo: TokenInfo,
@Param("reviewId") reviewid: number
) {
return await this.reviewService.reviewUnLike(
tokenInfo,
reviewid
);
}
}
12 changes: 12 additions & 0 deletions be/src/review/review.like.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DataSource, IsNull, Repository, Not } from "typeorm";
import { ConflictException, Injectable } from "@nestjs/common";
import { ReviewInfoEntity } from "./entities/review.entity";
import { ReviewLikeEntity } from "./entities/review.like.entity";
import { String } from "aws-sdk/clients/cloudhsm";

@Injectable()
export class ReviewLikeRepository extends Repository<ReviewLikeEntity> {
constructor(private dataSource: DataSource) {
super(ReviewLikeEntity, dataSource.createEntityManager());
}
}
8 changes: 6 additions & 2 deletions be/src/review/review.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Module } from "@nestjs/common";
import { ReviewRepository } from "./review.repository";
import { ReviewController } from "./review.controller";
import { ReviewService } from "./review.service";
import { ReviewLikeRepository } from "./review.like.repository";

@Module({
providers: [ReviewRepository],
controllers: [ReviewController],
providers: [ReviewRepository, ReviewLikeRepository, ReviewService],
exports: [ReviewRepository],
})
export class ReviewModule {}
export class ReviewModule { }
126 changes: 126 additions & 0 deletions be/src/review/review.repository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,136 @@
import { DataSource, IsNull, Repository, Not } from "typeorm";
import { ConflictException, Injectable } from "@nestjs/common";
import { ReviewInfoEntity } from "./entities/review.entity";
import { TokenInfo } from "src/user/user.decorator";
import { SortInfoDto } from "src/utils/sortInfo.dto";

@Injectable()
export class ReviewRepository extends Repository<ReviewInfoEntity> {
constructor(private dataSource: DataSource) {
super(ReviewInfoEntity, dataSource.createEntityManager());
}

async getReviewIdsWithLikes(sort: string) {
if (sort === "ASC") {
return await this
.createQueryBuilder("review")
.leftJoin("review.reviewLikes", "reviewLike")
.select("review.id", "reviewId")
.addSelect("COUNT(reviewLike.isLike)", "likeCount")
.groupBy("review.id")
.orderBy("COUNT(CASE WHEN reviewLike.isLike = false THEN 1 ELSE NULL END)", "DESC")
.getRawMany();
}
else {
return await this
.createQueryBuilder("review")
.leftJoin("review.reviewLikes", "reviewLike")
.select("review.id", "reviewId")
.addSelect("COUNT(reviewLike.isLike)", "likeCount")
.groupBy("review.id")
.orderBy("COUNT(CASE WHEN reviewLike.isLike = true THEN 1 ELSE NULL END)", "DESC")
.getRawMany();
}
}
async getSortedReviews(getSortedReviewsDto: SortInfoDto, restaurantId: number, id: TokenInfo["id"], sortedReviewIds: number[]) {
const pageNumber = getSortedReviewsDto.page || 1;
const limitNumber = getSortedReviewsDto.limit || 10;
const skipNumber = (pageNumber - 1) * limitNumber;
if (getSortedReviewsDto && getSortedReviewsDto.sort === "TIME_DESC") {
const items = await this.createQueryBuilder("review")
.leftJoinAndSelect("review.user", "user")
.leftJoin("review.reviewLikes", "reviewLike", "reviewLike.userId = :userId", { userId: id })
.select([
"review.id",
"review.isCarVisit",
"review.transportationAccessibility",
"review.parkingArea",
"review.taste",
"review.service",
"review.restroomCleanliness",
"review.overallExperience",
"user.nickName as reviewer",
"review.createdAt",
"review.reviewImage",
"reviewLike.isLike as isLike",
])
.where("review.restaurant_id = :restaurantId", {
restaurantId: restaurantId,
})
.orderBy("review.createdAt", "DESC")
.skip(skipNumber)
.take(limitNumber + 1)
.getRawMany();

const hasNext = items.length > limitNumber;
const resultItems = hasNext ? items.slice(0, -1) : items;

return { hasNext, items: resultItems };
}
else if (getSortedReviewsDto && getSortedReviewsDto.sort === "TIME_ASC") {
const items = await this.createQueryBuilder("review")
.leftJoinAndSelect("review.user", "user")
.leftJoin("review.reviewLikes", "reviewLike", "reviewLike.userId = :userId", { userId: id })
.select([
"review.id",
"review.isCarVisit",
"review.transportationAccessibility",
"review.parkingArea",
"review.taste",
"review.service",
"review.restroomCleanliness",
"review.overallExperience",
"user.nickName as reviewer",
"review.createdAt",
"review.reviewImage",
"reviewLike.isLike as isLike",
])
.where("review.restaurant_id = :restaurantId", {
restaurantId: restaurantId,
})
.orderBy("review.createdAt", "ASC")
.skip(skipNumber)
.take(limitNumber + 1)
.getRawMany();

const hasNext = items.length > limitNumber;
const resultItems = hasNext ? items.slice(0, -1) : items;

return { hasNext, items: resultItems };
}
else {
if (sortedReviewIds.length) {
const items = await this.createQueryBuilder("review")
.leftJoinAndSelect("review.user", "user")
.leftJoin("review.reviewLikes", "reviewLike", "reviewLike.userId = :userId", { userId: id })
.select([
"review.id",
"review.isCarVisit",
"review.transportationAccessibility",
"review.parkingArea",
"review.taste",
"review.service",
"review.restroomCleanliness",
"review.overallExperience",
"user.nickName as reviewer",
"review.createdAt",
"review.reviewImage",
"reviewLike.isLike as isLike",
])
.where("review.id IN (:...sortedReviewIds)", { sortedReviewIds })
.andWhere("review.restaurant_id = :restaurantId", { restaurantId: restaurantId })
.skip(skipNumber)
.take(limitNumber + 1)
.getRawMany();
const sortedItems = sortedReviewIds
.map(id => items.find(item => item.review_id === id))
.filter(item => item !== undefined);

const hasNext = sortedItems.length > limitNumber;
const resultItems = hasNext ? sortedItems.slice(0, -1) : sortedItems;

return { hasNext, items: resultItems };
}
}
}
}
18 changes: 18 additions & 0 deletions be/src/review/review.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ReviewService } from './review.service';

describe('ReviewService', () => {
let service: ReviewService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ReviewService],
}).compile();

service = module.get<ReviewService>(ReviewService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Loading

0 comments on commit 82e9999

Please sign in to comment.