Skip to content

Commit

Permalink
Merge pull request #8 from snuhcs-course/backend/feat/review
Browse files Browse the repository at this point in the history
Backend/feat/review
  • Loading branch information
mirlee0304 authored Oct 24, 2023
2 parents 4e85a88 + adb3d2e commit e98e3f1
Show file tree
Hide file tree
Showing 16 changed files with 529 additions and 14 deletions.
6 changes: 6 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.3.1",
"geolib": "^3.3.4",
"jsonwebtoken": "^9.0.2",
"luxon": "^3.4.3",
"multer": "^1.4.5-lts.1",
Expand Down
16 changes: 16 additions & 0 deletions backend/src/review/dtos/in-dtos/review-adjacent-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { IsNumber } from 'class-validator';
import { Transform, Type } from 'class-transformer';

export class ReviewAdjacentQueryDto {
@IsNumber()
@Type(() => Number)
latitude: number;

@IsNumber()
@Type(() => Number)
longitude: number;

@IsNumber()
@Type(() => Number)
distance: number;
}
12 changes: 12 additions & 0 deletions backend/src/review/dtos/out-dtos/restaurantList.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RestaurantDto } from './restaurant.dto';
import { RestaurantEntity } from '../../models/restaurant.entity';

export class RestaurantListDto {
private restaurantList: RestaurantDto[];

constructor(restaurantList: RestaurantEntity[]) {
this.restaurantList = restaurantList.map(
(restaurant) => new RestaurantDto(restaurant),
);
}
}
25 changes: 24 additions & 1 deletion backend/src/review/repositories/review.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReviewEntity } from '../models/review.entity';
import { Repository } from 'typeorm';
import { FindManyOptions } from 'typeorm/find-options/FindManyOptions';
import { FindOptionsWhere } from 'typeorm/find-options/FindOptionsWhere';
import { UserEntity } from '../../user/models/user.entity';

@CustomRepository(ReviewEntity)
export class ReviewRepository extends Repository<ReviewEntity> {
Expand Down Expand Up @@ -35,13 +36,35 @@ export class ReviewRepository extends Repository<ReviewEntity> {
return find[0];
}

private findFull(options: FindOptionsWhere<ReviewEntity>) {
findFull(options: FindOptionsWhere<ReviewEntity>) {
return this.find({
where: options,
relations: {
images: true,
user: true,
restaurant: true,
},
order: {
id: 'DESC',
},
});
}

findOfUser(user: UserEntity) {
return this.findFull({
user: {
id: user.id,
},
});
}

findRandomReviews(limit: number) {
return this.createQueryBuilder('review')
.leftJoinAndSelect('review.images', 'image')
.leftJoinAndSelect('review.user', 'user')
.leftJoinAndSelect('review.restaurant', 'restaurant')
.orderBy('RANDOM()')
.limit(limit)
.getMany();
}
}
31 changes: 31 additions & 0 deletions backend/src/review/review.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
NotFoundException,
Param,
Post,
Query,
Req,
UploadedFile,
UseGuards,
Expand All @@ -24,6 +25,10 @@ import { ReviewRepository } from './repositories/review.repository';
import { ReviewListDto } from './dtos/out-dtos/reviewList.dto';
import { ImageUploadDto } from './dtos/out-dtos/imageUpload.dto';
import { ReviewDetailDto } from './dtos/out-dtos/reviewDetail.dto';
import { UserEntity } from '../user/models/user.entity';
import { UserRepository } from '../user/repostiories/user.repository';
import { ReviewAdjacentQueryDto } from './dtos/in-dtos/review-adjacent-query.dto';
import { RestaurantListDto } from './dtos/out-dtos/restaurantList.dto';

@ApiTags('reviews')
@Controller('reviews')
Expand Down Expand Up @@ -55,6 +60,32 @@ export class ReviewController {
return new ReviewListDto(reviews);
}

@UseGuards(JwtAccessGuard)
@Get('/my')
async getMyReview(@Req() { user }: UserRequest) {
const reviews = await this.reviewRepository.findOfUser(user);
return new ReviewListDto(reviews);
}

@UseGuards(JwtAccessGuard)
@Get('/random')
async getReviewRandom() {
const limit = 5; // Define the number of random reviews you want to retrieve
const randomReviews = await this.reviewRepository.findRandomReviews(limit);
return new ReviewListDto(randomReviews);
}

@UseGuards(JwtAccessGuard)
@Get('/adjacent/restaurants')
async getReviewAdjacent(
@Req() { user }: UserRequest,
@Query() data: ReviewAdjacentQueryDto,
) {
const restaurants = await this.reviewService.getAdjacentRestaurant(data);

return new RestaurantListDto(restaurants);
}

@UseGuards(JwtAccessGuard)
@Get('/:reviewId')
async getReviewDetail(
Expand Down
17 changes: 17 additions & 0 deletions backend/src/review/review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { RestaurantRepository } from './repositories/restaurant.repository';
import { ImageRepository } from './repositories/image.repository';
import { In } from 'typeorm';
import { ReviewEntity } from './models/review.entity';
import { ReviewAdjacentQueryDto } from './dtos/in-dtos/review-adjacent-query.dto';
import { getDistance } from 'geolib';

@Injectable()
export class ReviewService {
Expand Down Expand Up @@ -37,4 +39,19 @@ export class ReviewService {
images,
}).save();
}

async getAdjacentRestaurant(data: ReviewAdjacentQueryDto) {
const { longitude, latitude, distance } = data;
const restaurants = await this.restaurantRepository.find({});
const KILOMETER = 1000;

return restaurants.filter(
(restaurant) =>
getDistance(
{ latitude, longitude },
{ latitude: restaurant.latitude, longitude: restaurant.longitude },
) <=
distance * KILOMETER,
);
}
}
126 changes: 126 additions & 0 deletions backend/src/test/review/adjacent-restaurant.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from '../../app.module';
import { Test } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { appSetting } from '../../main';
import * as supertest from 'supertest';
import { UserEntity } from '../../user/models/user.entity';
import { UserFixture } from '../fixture/user.fixture';
import { HttpStatus } from '@nestjs/common';
import { RestaurantEntity } from '../../review/models/restaurant.entity';
import { RestaurantFixture } from '../fixture/restaurant.fixture';
import { ReviewEntity } from '../../review/models/review.entity';
import { ReviewFixture } from '../fixture/review.fixture';
import { ImageFixture } from '../fixture/image.fixture';
import {
validateRestaurantList,
validateReview,
validateReviewList,
} from './validateReviewList';

describe('Get adjacent restaurant test', () => {
let testServer: NestExpressApplication;
let dataSource: DataSource;
let user: UserEntity;
let accessToken: string;
let restaurant: RestaurantEntity;
let review: ReviewEntity;

beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();

testServer = module.createNestApplication<NestExpressApplication>();
dataSource = testServer.get(DataSource);
await dataSource.synchronize(true);
appSetting(testServer);

await testServer.init();
});

beforeEach(async () => {
await dataSource.synchronize(true);

user = await UserFixture.create({
name: 'hi',
username: 'hello',
password: 'world',
});

const { body } = await supertest(testServer.getHttpServer())
.post('/auth/login')
.send({
username: 'hello',
password: 'world',
})
.expect(HttpStatus.CREATED);

accessToken = body.accessToken;

restaurant = await RestaurantFixture.create({});
const image = await ImageFixture.create({});
review = await ReviewFixture.create({
restaurant,
images: [image],
user,
});
});

it('unauthorized', async () => {
await supertest(testServer.getHttpServer())
.get(
`/reviews/adjacent/restaurants?longitude=127.0&latitude=37.0&distance=1`,
)
.expect(HttpStatus.UNAUTHORIZED);
});

it('OK', async () => {
await supertest(testServer.getHttpServer())
.get(
`/reviews/adjacent/restaurants?longitude=127.0&latitude=37.0&distance=1`,
)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);
});

it('DTO check', async () => {
restaurant.longitude = 127.0;
restaurant.latitude = 37.0;
await restaurant.save();

const { body } = await supertest(testServer.getHttpServer())
.get(
`/reviews/adjacent/restaurants?longitude=127.0&latitude=37.0&distance=1`,
)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);

validateRestaurantList(body);
});

// TODO : unittest
it('멀리 떨어지면 안잡한다', async () => {
restaurant.longitude = 127.0;
restaurant.latitude = 37.018018;
await restaurant.save();

const { body } = await supertest(testServer.getHttpServer())
.get(
`/reviews/adjacent/restaurants?longitude=127.0&latitude=37.0&distance=1`,
)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);

expect(body.restaurantList.length).toEqual(0);

const { body: body2 } = await supertest(testServer.getHttpServer())
.get(
`/reviews/adjacent/restaurants?longitude=127.0&latitude=37.0&distance=3`,
)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatus.OK);

expect(body2.restaurantList.length).toEqual(1);
});
});
2 changes: 1 addition & 1 deletion backend/src/test/review/create-review.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { RestaurantEntity } from '../../review/models/restaurant.entity';
import { RestaurantFixture } from '../fixture/restaurant.fixture';
import { ReviewEntity } from '../../review/models/review.entity';

describe('Review test', () => {
describe('Create Review test', () => {
let testServer: NestExpressApplication;
let dataSource: DataSource;
let user: UserEntity;
Expand Down
Loading

0 comments on commit e98e3f1

Please sign in to comment.