-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
지도/코스에 핀 추가 API 구현 #67
Changes from 16 commits
a29b150
41dd446
03f48a6
2c6d984
2d2de4d
8072f55
f40b401
20fd83e
3f2ac9b
4c71999
6f9bfe0
7c8058c
3655de1
2415a32
7285b1d
4ed9e62
d6a9b99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
-- USER 데이터 삽입 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3: |
||
INSERT INTO USER (provider, nickname, oauth_id, role, profile_image_url) | ||
VALUES ('google', 'user1', 'oauth1', 'USER', 'https://example.com/profile1.jpg'), | ||
('google', 'user2', 'oauth2', 'USER', 'https://example.com/profile2.jpg'), | ||
('google', 'user3', 'oauth3', 'USER', 'https://example.com/profile3.jpg'), | ||
('google', 'user4', 'oauth4', 'USER', 'https://example.com/profile4.jpg'), | ||
('google', 'user5', 'oauth5', 'USER', 'https://example.com/profile5.jpg'); | ||
|
||
-- PLACE 데이터 삽입 | ||
INSERT INTO PLACE (google_place_id, name, thumbnail_url, rating, longitude, latitude, formatted_address, description, | ||
detail_page_url) | ||
VALUES ('place1', 'Place 1', 'https://example.com/place1.jpg', 4.5, 127.001, 37.501, 'Seoul, South Korea', | ||
'Beautiful place 1', 'https://example.com/detail1'), | ||
('place2', 'Place 2', 'https://example.com/place2.jpg', 4.0, 127.002, 37.502, 'Seoul, South Korea', | ||
'Beautiful place 2', 'https://example.com/detail2'), | ||
('place3', 'Place 3', 'https://example.com/place3.jpg', 4.2, 127.003, 37.503, 'Seoul, South Korea', | ||
'Beautiful place 3', 'https://example.com/detail3'), | ||
('place4', 'Place 4', 'https://example.com/place4.jpg', 4.1, 127.004, 37.504, 'Seoul, South Korea', | ||
'Beautiful place 4', 'https://example.com/detail4'), | ||
('place5', 'Place 5', 'https://example.com/place5.jpg', 3.9, 127.005, 37.505, 'Seoul, South Korea', | ||
'Beautiful place 5', 'https://example.com/detail5'), | ||
('place6', 'Place 6', 'https://example.com/place6.jpg', 4.3, 127.006, 37.506, 'Seoul, South Korea', | ||
'Beautiful place 6', 'https://example.com/detail6'), | ||
('place7', 'Place 7', 'https://example.com/place7.jpg', 4.4, 127.007, 37.507, 'Seoul, South Korea', | ||
'Beautiful place 7', 'https://example.com/detail7'), | ||
('place8', 'Place 8', 'https://example.com/place8.jpg', 4.6, 127.008, 37.508, 'Seoul, South Korea', | ||
'Beautiful place 8', 'https://example.com/detail8'), | ||
('place9', 'Place 9', 'https://example.com/place9.jpg', 4.7, 127.009, 37.509, 'Seoul, South Korea', | ||
'Beautiful place 9', 'https://example.com/detail9'), | ||
('place10', 'Place 10', 'https://example.com/place10.jpg', 4.8, 127.010, 37.510, 'Seoul, South Korea', | ||
'Beautiful place 10', 'https://example.com/detail10'); | ||
|
||
-- MAP 데이터 삽입 | ||
INSERT INTO MAP (user_id, thumbnail_url, title, is_public, description) | ||
VALUES (1, 'https://example.com/map1.jpg', 'Map 1', TRUE, 'Description for Map 1'), | ||
(2, 'https://example.com/map2.jpg', 'Map 2', FALSE, 'Description for Map 2'), | ||
(3, 'https://example.com/map3.jpg', 'Map 3', TRUE, 'Description for Map 3'), | ||
(4, 'https://example.com/map4.jpg', 'Map 4', FALSE, 'Description for Map 4'), | ||
(5, 'https://example.com/map5.jpg', 'Map 5', TRUE, 'Description for Map 5'), | ||
(1, 'https://example.com/map6.jpg', 'Map 6', TRUE, 'Description for Map 6'), | ||
(2, 'https://example.com/map7.jpg', 'Map 7', FALSE, 'Description for Map 7'), | ||
(3, 'https://example.com/map8.jpg', 'Map 8', TRUE, 'Description for Map 8'), | ||
(4, 'https://example.com/map9.jpg', 'Map 9', FALSE, 'Description for Map 9'), | ||
(5, 'https://example.com/map10.jpg', 'Map 10', TRUE, 'Description for Map 10'); | ||
|
||
-- COURSE 데이터 삽입 | ||
INSERT INTO COURSE (user_id, thumbnail_url, title, is_public, description) | ||
VALUES (1, 'https://example.com/course1.jpg', 'Course 1', TRUE, 'Description for Course 1'), | ||
(2, 'https://example.com/course2.jpg', 'Course 2', FALSE, 'Description for Course 2'), | ||
(3, 'https://example.com/course3.jpg', 'Course 3', TRUE, 'Description for Course 3'), | ||
(4, 'https://example.com/course4.jpg', 'Course 4', FALSE, 'Description for Course 4'), | ||
(5, 'https://example.com/course5.jpg', 'Course 5', TRUE, 'Description for Course 5'), | ||
(1, 'https://example.com/course6.jpg', 'Course 6', TRUE, 'Description for Course 6'), | ||
(2, 'https://example.com/course7.jpg', 'Course 7', FALSE, 'Description for Course 7'), | ||
(3, 'https://example.com/course8.jpg', 'Course 8', TRUE, 'Description for Course 8'), | ||
(4, 'https://example.com/course9.jpg', 'Course 9', FALSE, 'Description for Course 9'), | ||
(5, 'https://example.com/course10.jpg', 'Course 10', TRUE, 'Description for Course 10'); | ||
|
||
-- MAP_PLACE 데이터 삽입 | ||
INSERT INTO MAP_PLACE (place_id, map_id, description) | ||
VALUES (1, 1, 'Place 1 in Map 1'), | ||
(2, 2, 'Place 2 in Map 2'), | ||
(3, 3, 'Place 3 in Map 3'), | ||
(4, 4, 'Place 4 in Map 4'), | ||
(5, 5, 'Place 5 in Map 5'), | ||
(6, 6, 'Place 6 in Map 6'), | ||
(7, 7, 'Place 7 in Map 7'), | ||
(8, 8, 'Place 8 in Map 8'), | ||
(9, 9, 'Place 9 in Map 9'), | ||
(10, 10, 'Place 10 in Map 10'), | ||
(1, 2, 'Place 1 in Map 2'), | ||
(2, 3, 'Place 2 in Map 3'), | ||
(3, 4, 'Place 3 in Map 4'); | ||
|
||
-- COURSE_PLACE 데이터 삽입 | ||
INSERT INTO COURSE_PLACE (`order`, place_id, course_id, description) | ||
VALUES (1, 1, 1, 'Place 1 in Course 1'), | ||
(2, 2, 2, 'Place 2 in Course 2'), | ||
(3, 3, 3, 'Place 3 in Course 3'), | ||
(4, 4, 4, 'Place 4 in Course 4'), | ||
(5, 5, 5, 'Place 5 in Course 5'), | ||
(6, 6, 6, 'Place 6 in Course 6'), | ||
(7, 7, 7, 'Place 7 in Course 7'), | ||
(8, 8, 8, 'Place 8 in Course 8'), | ||
(9, 9, 9, 'Place 9 in Course 9'), | ||
(10, 10, 10, 'Place 10 in Course 10'), | ||
(2, 1, 3, 'Place 1 in Course 3'), | ||
(3, 2, 4, 'Place 2 in Course 4'), | ||
(4, 3, 5, 'Place 3 in Course 5'); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -7,10 +7,12 @@ import { | |||||
Delete, | ||||||
Param, | ||||||
Patch, | ||||||
Put, | ||||||
} from '@nestjs/common'; | ||||||
import { CreateCourseRequest } from './dto/CreateCourseRequest'; | ||||||
import { UpdateCourseInfoRequest } from './dto/UpdateCourseInfoRequest'; | ||||||
import { CourseService } from './course.service'; | ||||||
import { SetPlacesOfCourseRequest } from './dto/AddPlaceToCourseRequest'; | ||||||
|
||||||
@Controller('/courses') | ||||||
export class CourseController { | ||||||
|
@@ -38,9 +40,15 @@ export class CourseController { | |||||
return await this.courseService.createCourse(userId, createCourseRequest); | ||||||
} | ||||||
|
||||||
@Delete('/:id') | ||||||
async deleteCourse(@Param('id') id: number) { | ||||||
return await this.courseService.deleteCourse(id); | ||||||
@Put('/:id/places') | ||||||
async SetPlacesOfCourse( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p1: 네이밍 수정바랍니다!
Suggested change
|
||||||
@Param('id') id: number, | ||||||
@Body() setPlacesOfCourseRequest: SetPlacesOfCourseRequest, | ||||||
) { | ||||||
return await this.courseService.setPlacesOfCourse( | ||||||
id, | ||||||
setPlacesOfCourseRequest, | ||||||
); | ||||||
} | ||||||
|
||||||
@Patch('/:id/info') | ||||||
|
@@ -60,4 +68,9 @@ export class CourseController { | |||||
await this.courseService.updateCourseVisibility(id, isPublic); | ||||||
return { id, isPublic }; | ||||||
} | ||||||
|
||||||
@Delete('/:id') | ||||||
async deleteCourse(@Param('id') id: number) { | ||||||
return await this.courseService.deleteCourse(id); | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,18 +2,24 @@ import { Injectable } from '@nestjs/common'; | |
import { CourseRepository } from './course.repository'; | ||
import { User } from '../user/entity/user.entity'; | ||
import { CreateCourseRequest } from './dto/CreateCourseRequest'; | ||
import { InjectRepository } from '@nestjs/typeorm'; | ||
import { Repository } from 'typeorm'; | ||
import { CourseListResponse } from './dto/CourseListResponse'; | ||
import { CourseDetailResponse } from './dto/CourseDetailResponse'; | ||
import { | ||
CourseDetailResponse, | ||
getPlacesResponseOfCourseWithOrder, | ||
} from './dto/CourseDetailResponse'; | ||
import { CourseNotFoundException } from './exception/CourseNotFoundException'; | ||
import { UpdateCourseInfoRequest } from './dto/UpdateCourseInfoRequest'; | ||
import { SetPlacesOfCourseRequest } from './dto/AddPlaceToCourseRequest'; | ||
import { PlaceRepository } from '../place/place.repository'; | ||
import { UserRepository } from '../user/user.repository'; | ||
import { InvalidPlaceToCourseException } from './exception/InvalidPlaceToCourseException'; | ||
|
||
@Injectable() | ||
export class CourseService { | ||
constructor( | ||
private readonly courseRepository: CourseRepository, | ||
@InjectRepository(User) private readonly userRepository: Repository<User>, | ||
private readonly placeRepository: PlaceRepository, | ||
private readonly userRepository: UserRepository, | ||
) { | ||
// Todo. 로그인 기능 완성 후 제거 | ||
const testUser = new User('test', 'test', 'test', 'test'); | ||
|
@@ -34,7 +40,7 @@ export class CourseService { | |
const publicMaps = maps.filter((map) => map.isPublic); | ||
|
||
return { | ||
maps: await Promise.all(publicMaps.map(CourseListResponse.from)), | ||
courses: await Promise.all(publicMaps.map(CourseListResponse.from)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: 제가 지식이 부족해서.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 이거 저도 단일 인자고, static 메서드면 되는 것 같습니다 |
||
totalPages: Math.ceil(totalCount / pageSize), | ||
currentPage: page, | ||
}; | ||
|
@@ -53,7 +59,7 @@ export class CourseService { | |
); | ||
|
||
return { | ||
maps: await Promise.all(ownMaps.map(CourseListResponse.from)), | ||
courses: await Promise.all(ownMaps.map(CourseListResponse.from)), | ||
totalPages: Math.ceil(totalCount / pageSize), | ||
currentPage: page, | ||
}; | ||
|
@@ -97,4 +103,38 @@ export class CourseService { | |
if (!(await this.courseRepository.existById(id))) | ||
throw new CourseNotFoundException(id); | ||
} | ||
|
||
async setPlacesOfCourse( | ||
id: number, | ||
setPlacesOfCourseRequest: SetPlacesOfCourseRequest, | ||
) { | ||
const course = await this.courseRepository.findById(id); | ||
if (!course) throw new CourseNotFoundException(id); | ||
|
||
await this.checkPlacesExist( | ||
setPlacesOfCourseRequest.places.map((p) => p.placeId), | ||
); | ||
Comment on lines
+114
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: 반환값은 어디서 쓰이나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤 함수의 반환값인가요? |
||
|
||
course.setPlaces(setPlacesOfCourseRequest.places); | ||
await this.courseRepository.save(course); // Todo. Q.바로 장소 조회하면 장소 정보가 없음.. (장소 참조만 객체에 저장했기 때문) | ||
const reloadedCourse = await this.courseRepository.findById(course.id); | ||
|
||
return { | ||
places: await getPlacesResponseOfCourseWithOrder(reloadedCourse), | ||
}; | ||
} | ||
Comment on lines
+119
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
그 이후 place의 온전한 정보를 응답하려니, 위의 참조로 충분하지 않고, 이후에 |
||
|
||
private async checkPlacesExist(placeIds: number[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래의 것과 비슷한 리뷰 같은데요,
|
||
const notExistsPlaceIds = await Promise.all( | ||
placeIds.map(async (placeId) => { | ||
const exists = await this.placeRepository.existById(placeId); | ||
return exists ? null : placeId; | ||
}), | ||
); | ||
|
||
const invalidIds = notExistsPlaceIds.filter((placeId) => placeId !== null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p3: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 그렇게 작성했다가, |
||
if (invalidIds.length > 0) { | ||
throw new InvalidPlaceToCourseException(invalidIds); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { | ||
IsNumber, | ||
IsNotEmpty, | ||
IsString, | ||
IsArray, | ||
ValidateNested, | ||
Validate, | ||
} from 'class-validator'; | ||
import { Type } from 'class-transformer'; | ||
import { IsNotConsecutiveDuplicatePlace } from '../pipes/IsNotConsecutiveDuplicatePlace'; | ||
|
||
export class SetPlacesOfCourseRequestItem { | ||
@IsNumber() | ||
@IsNotEmpty() | ||
placeId: number; | ||
|
||
@IsString() | ||
comment?: string; | ||
} | ||
|
||
export class SetPlacesOfCourseRequest { | ||
@IsArray() | ||
@ValidateNested({ each: true }) | ||
@Type(() => SetPlacesOfCourseRequestItem) | ||
@Validate(IsNotConsecutiveDuplicatePlace) | ||
places: SetPlacesOfCourseRequestItem[]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { BaseException } from '../../common/exception/BaseException'; | ||
import { HttpStatus } from '@nestjs/common'; | ||
|
||
export class ConsecutivePlaceException extends BaseException { | ||
constructor() { | ||
super({ | ||
code: 904, | ||
message: '동일한 장소는 연속된 순서로 추가할 수 없습니다.', | ||
status: HttpStatus.BAD_REQUEST, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
q: 나중에 바뀌는거죠?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOT NULL
로 바뀌냐는 질문인가요?아니오! ORM 으로 관계 해제했을 때 null 로 업데이트 되는 과정에서
NOT NULL
제약조건으로 오류가 발생해 삭제했습니다.orphanremoval 옵션을 사용해서 참조가 null 이 되었을 경우 레코드를 삭제하도록어플리케이션 레벨에서 제어할 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@koomchang 지금보니 이거 다시 추가해도 될 것 같아요!
update set null 쿼리가 안나가게 할 수 있겠네요.
제가
cascade
,orphanedRowAction
,onDelete
관련 정리해서 개발 일지에 공유할게요!