diff --git a/backend/src/map/dto/AddPlaceToMapRequest.ts b/backend/src/map/dto/AddPlaceToMapRequest.ts new file mode 100644 index 00000000..0d220b05 --- /dev/null +++ b/backend/src/map/dto/AddPlaceToMapRequest.ts @@ -0,0 +1,9 @@ +import { IsNumber, IsString } from 'class-validator'; + +export class AddPlaceToMapRequest { + @IsNumber() + placeId: number; + + @IsString() + comment?: string; +} diff --git a/backend/src/map/entity/map-place.entity.ts b/backend/src/map/entity/map-place.entity.ts index 65f0f28b..6b9c301f 100644 --- a/backend/src/map/entity/map-place.entity.ts +++ b/backend/src/map/entity/map-place.entity.ts @@ -5,6 +5,9 @@ import { Map } from './map.entity'; @Entity() export class MapPlace extends BaseEntity { + @Column() + placeId: number; + @ManyToOne(() => Place, { onDelete: 'CASCADE', lazy: true }) @JoinColumn({ name: 'place_id' }) place: Promise; @@ -18,4 +21,13 @@ export class MapPlace extends BaseEntity { @Column('text', { nullable: true }) description?: string; + + static of(placeId: number, map: Map, description?: string) { + const place = new MapPlace(); + place.map = map; + place.placeId = placeId; + place.place = Promise.resolve({ id: placeId } as Place); + place.description = description; + return place; + } } diff --git a/backend/src/map/entity/map.entity.ts b/backend/src/map/entity/map.entity.ts index 0ec6e628..bc58ccda 100644 --- a/backend/src/map/entity/map.entity.ts +++ b/backend/src/map/entity/map.entity.ts @@ -46,6 +46,18 @@ export class Map extends BaseEntity { return this.mapPlaces.length; } + addPlace(placeId: number, description: string) { + this.mapPlaces.push(MapPlace.of(placeId, this, description)); + } + + async deletePlace(placeId: number) { + this.mapPlaces = this.mapPlaces.filter((p) => p.placeId !== placeId); + } + + async hasPlace(placeId: number) { + return this.mapPlaces.some((p) => p.placeId === placeId); + } + async getPlacesWithComment() { return await Promise.all( this.mapPlaces.map(async (mapPlace) => ({ diff --git a/backend/src/map/map.controller.ts b/backend/src/map/map.controller.ts index bae1cf3e..023fd80a 100644 --- a/backend/src/map/map.controller.ts +++ b/backend/src/map/map.controller.ts @@ -11,6 +11,7 @@ import { import { MapService } from './map.service'; import { CreateMapRequest } from './dto/CreateMapRequest'; import { UpdateMapInfoRequest } from './dto/UpdateMapInfoRequest'; +import { AddPlaceToMapRequest } from './dto/AddPlaceToMapRequest'; @Controller('/maps') export class MapController { @@ -38,6 +39,23 @@ export class MapController { return await this.mapService.createMap(userId, createMapRequest); } + @Post('/:id/places') + async addPlaceToMap( + @Param('id') id: number, + @Body() addPlaceToMapRequest: AddPlaceToMapRequest, + ) { + const { placeId, comment } = addPlaceToMapRequest; + return await this.mapService.addPlace(id, placeId, comment); + } + + @Delete('/:id/places/:placeId') + async deletePlaceFromMap( + @Param('id') id: number, + @Param('placeId') placeId: number, + ) { + return await this.mapService.deletePlace(id, placeId); + } + @Patch('/:id/info') async updateMapInfo( @Param('id') id: number, diff --git a/backend/src/map/map.module.ts b/backend/src/map/map.module.ts index 8bcb6ddf..912cf6e6 100644 --- a/backend/src/map/map.module.ts +++ b/backend/src/map/map.module.ts @@ -3,9 +3,10 @@ import { MapService } from './map.service'; import { MapController } from './map.controller'; import { UserModule } from '../user/user.module'; import { MapRepository } from './map.repository'; +import { PlaceModule } from '../place/place.module'; @Module({ - imports: [UserModule], + imports: [UserModule, PlaceModule], controllers: [MapController], providers: [MapService, MapRepository], }) diff --git a/backend/src/map/map.service.ts b/backend/src/map/map.service.ts index 41f7893e..6f93ed70 100644 --- a/backend/src/map/map.service.ts +++ b/backend/src/map/map.service.ts @@ -7,12 +7,17 @@ import { UserRepository } from '../user/user.repository'; import { UpdateMapInfoRequest } from './dto/UpdateMapInfoRequest'; import { CreateMapRequest } from './dto/CreateMapRequest'; import { MapNotFoundException } from './exception/MapNotFoundException'; +import { DuplicatePlaceToMapException } from './exception/DuplicatePlaceToMapException'; +import { PlaceRepository } from '../place/place.repository'; +import { InvalidPlaceToMapException } from './exception/InvalidPlaceToMapException'; +import { Map } from './entity/map.entity'; @Injectable() export class MapService { constructor( private readonly mapRepository: MapRepository, private readonly userRepository: UserRepository, + private readonly placeRepository: PlaceRepository, ) { // Todo. 로그인 기능 완성 후 제거 const testUser = new User('test', 'test', 'test', 'test'); @@ -97,4 +102,38 @@ export class MapService { if (!(await this.mapRepository.existById(id))) throw new MapNotFoundException(id); } + + async addPlace(id: number, placeId: number, comment?: string) { + const map = await this.mapRepository.findById(id); + if (!map) throw new MapNotFoundException(id); + await this.checkPlaceCanAddToMap(placeId, map); + + map.addPlace(placeId, comment); + await this.mapRepository.save(map); + + return { + savedPlaceId: placeId, + comment: comment, + }; + } + + private async checkPlaceCanAddToMap(placeId: number, map: Map) { + if (!(await this.placeRepository.existById(placeId))) { + throw new InvalidPlaceToMapException(placeId); + } + + if (await map.hasPlace(placeId)) { + throw new DuplicatePlaceToMapException(placeId); + } + } + + async deletePlace(id: number, placeId: number) { + const map = await this.mapRepository.findById(id); + if (!map) throw new MapNotFoundException(id); + + map.deletePlace(placeId); + await this.mapRepository.save(map); + + return { deletedId: placeId }; + } }