From 5b727acde6374331c645e364781dffdd1d3a605a Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 03:41:46 +0900 Subject: [PATCH 1/7] =?UTF-8?q?:recycle:=20git=20action=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit live 및 develop 서버 분리에 따른 서버 분리 필요 --- .github/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 4f218f1..971edd4 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -3,7 +3,7 @@ name: Music Spot Project on: push: - branches: ["BE/release"] + branches: ["main"] jobs: build: runs-on: ubuntu-latest From ee3b66a2be29e061ab16a377eca0447091d50889 Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 03:49:09 +0900 Subject: [PATCH 2/7] =?UTF-8?q?:sparkles:=20(V2)=20journey=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20util=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geo data 양식 수정([[1,2],[1,2]] -> 1 2,1 2)에 따른 util 함수 변경 --- .../decorator/coordinate.v2.decorator.ts | 38 ++++++++++++ .../src/common/util/coordinate.v2.util.ts | 62 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 BE/musicspot/src/common/decorator/coordinate.v2.decorator.ts create mode 100644 BE/musicspot/src/common/util/coordinate.v2.util.ts diff --git a/BE/musicspot/src/common/decorator/coordinate.v2.decorator.ts b/BE/musicspot/src/common/decorator/coordinate.v2.decorator.ts new file mode 100644 index 0000000..87ef53c --- /dev/null +++ b/BE/musicspot/src/common/decorator/coordinate.v2.decorator.ts @@ -0,0 +1,38 @@ +import { + registerDecorator, + ValidationOptions, + ValidationArguments, +} from 'class-validator'; +import { isLinestring, isPointString } from '../util/coordinate.v2.util'; + +export function IsCoordinateV2(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'isCoordinateV2', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(receiveValue: string, args: ValidationArguments) { + return isPointString(receiveValue); + }, + }, + }); + }; +} + +export function IsCoordinatesV2(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + name: 'isCoordinatesV2', + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + validator: { + validate(receiveValue: string, args: ValidationArguments) { + return isLinestring(receiveValue); + }, + }, + }); + }; +} diff --git a/BE/musicspot/src/common/util/coordinate.v2.util.ts b/BE/musicspot/src/common/util/coordinate.v2.util.ts new file mode 100644 index 0000000..3734a74 --- /dev/null +++ b/BE/musicspot/src/common/util/coordinate.v2.util.ts @@ -0,0 +1,62 @@ +export const isPointString = (pointString: string): boolean => { + const regex = /^\d+.\d+\s\d+.\d+$/; + if (!pointString.match(regex)) { + return false; + } + if (!isCorrectCoordinateRange(pointString)) { + return false; + } + return true; +}; + +export const isLinestring = (lineString: string): boolean => { + const regex: RegExp = + /^\d+.\d+\s\d+.\d+,(\d+.\d+\s\d+.\d+,)*\d+.\d+\s\d+.\d+$/; + console.log(':ASD'); + if (!lineString.match(regex)) { + return false; + } + + const points = lineString.split(','); + for (let i = 0; i < points.length; i++) { + if (!isCorrectCoordinateRange(points[i])) { + return false; + } + } + return true; +}; + +export const isCorrectCoordinateRange = (pointString: string): boolean => { + const [lat, lon] = pointString.split(' ').map((str) => Number(str)); + if (lat > 90 || lat < -90) { + return false; + } + if (lon > 180 || lon < -180) { + return false; + } + + return true; +}; + +export const parseCoordinateFromDtoToGeoV2 = (coordinate: string): string => { + // coordinate = 1 2 + return `POINT(${coordinate})`; +}; + +export const parseCoordinateFromGeoToDtoV2 = (coordinate: string): string => { + // coordinate = 'POINT(1 2)' + + const pointLen = 'POINT('.length; + return coordinate.slice(pointLen, -1); +}; + +export const parseCoordinatesFromDtoToGeoV2 = (coordinates: string): string => { + // coordinates = 1 2,3 4 + return `LINESTRING(${coordinates})`; +}; + +export const parseCoordinatesFromGeoToDtoV2 = (coordinates: string): string => { + // coordinates = 'LINESTRING(1 2,3 4)' + const pointLen = 'LINESTRING'.length; + return coordinates.slice(pointLen, -1); +}; From 127e9cbadf63d77d94a3908af4a3b9f8ec2da668 Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 03:50:51 +0900 Subject: [PATCH 3/7] =?UTF-8?q?:sparkles:=20(V2)=20journey=20dto=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geo data 양식 수정([[1,2],[1,2]] -> 1 2,1 2)에 따른 journey dto 수정 - startJourney.v2.dto.ts - coordinate 제거 - endJourney.v2.dto.ts - coordinates 양식 수정 --- .../src/journey/dto/v2/endJourney.v2.dto.ts | 88 +++++++++++++++++++ .../src/journey/dto/v2/startJourney.v2.dto.ts | 35 ++++++++ 2 files changed, 123 insertions(+) create mode 100644 BE/musicspot/src/journey/dto/v2/endJourney.v2.dto.ts create mode 100644 BE/musicspot/src/journey/dto/v2/startJourney.v2.dto.ts diff --git a/BE/musicspot/src/journey/dto/v2/endJourney.v2.dto.ts b/BE/musicspot/src/journey/dto/v2/endJourney.v2.dto.ts new file mode 100644 index 0000000..7d42f02 --- /dev/null +++ b/BE/musicspot/src/journey/dto/v2/endJourney.v2.dto.ts @@ -0,0 +1,88 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsString, + IsDateString, + IsNumber, + IsObject, +} from 'class-validator'; +import { IsCoordinatesV2 } from '../../../common/decorator/coordinate.v2.decorator'; + +export class EndJourneyReqDTOV2 { + @ApiProperty({ + example: 20, + description: '여정 id', + required: true, + }) + @IsNumber() + readonly journeyId: number; + + @ApiProperty({ + example: '37.555946 126.972384,37.555946 126.972384', + description: '위치 좌표', + required: true, + }) + @IsCoordinatesV2({ + message: + '잘못된 coordinates 형식입니다. Ex) 37.555946 126.972384,37.555946 126.972384', + }) + readonly coordinates: string; + + @ApiProperty({ + example: '2023-11-22T12:00:00Z', + description: '종료 timestamp', + required: true, + }) + @IsDateString() + readonly endTimestamp: string; + + @ApiProperty({ + example: '여정 제목', + description: '여정 제목', + required: true, + }) + @IsString() + readonly title: string; + + @IsObject() + @ApiProperty({ + description: '노래 정보', + required: true, + }) + readonly song: object; +} + +export class EndJourneyResDTOV2 { + @ApiProperty({ + example: 20, + description: '여정 id', + required: true, + }) + readonly journeyId: number; + + @ApiProperty({ + example: '37.555946 126.972384,37.555946 126.972384', + description: '위치 좌표', + required: true, + }) + readonly coordinates: string; + + @ApiProperty({ + example: '2023-11-22T15:30:00.000+09:00', + description: '여정 종료 시간', + required: true, + }) + readonly endTimestamp: string; + + @ApiProperty({ + example: 2, + description: '기록된 coordinate 수', + required: true, + }) + readonly numberOfCoordinates: number; + + @ApiProperty({ + description: '노래 정보', + required: true, + }) + readonly song: object; +} diff --git a/BE/musicspot/src/journey/dto/v2/startJourney.v2.dto.ts b/BE/musicspot/src/journey/dto/v2/startJourney.v2.dto.ts new file mode 100644 index 0000000..88b6452 --- /dev/null +++ b/BE/musicspot/src/journey/dto/v2/startJourney.v2.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsUUID } from 'class-validator'; +import { UUID } from 'crypto'; + +export class StartJourneyReqDTOV2 { + @ApiProperty({ + example: 'ab4068ef-95ed-40c3-be6d-3db35df866b9', + description: '사용자 id', + required: true, + }) + @IsUUID() + readonly userId: UUID; + + @ApiProperty({ + example: '2023-11-22T12:00:00Z', + description: '시작 timestamp', + required: true, + }) + @IsDateString() + readonly startTimestamp: string; +} + +export class StartJourneyResDTOV2 { + @ApiProperty({ + example: 20, + description: '저장한 journey id', + }) + readonly journeyId: number; + + @ApiProperty({ + example: '2023-11-22T12:00:00Z', + description: 'timestamp', + }) + readonly startTimestamp: string; +} From 3d8fccad37fd413ac447f802b827179322bce2e5 Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 03:51:38 +0900 Subject: [PATCH 4/7] =?UTF-8?q?:sparkles:=20(V2)=20coordinate=20=EC=96=91?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?controller=20=EB=B0=8F=20service=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../journey/controller/journey.controller.ts | 35 +- .../src/journey/service/journey.service.ts | 426 +++++++++++------- 2 files changed, 291 insertions(+), 170 deletions(-) diff --git a/BE/musicspot/src/journey/controller/journey.controller.ts b/BE/musicspot/src/journey/controller/journey.controller.ts index f29a917..e069871 100644 --- a/BE/musicspot/src/journey/controller/journey.controller.ts +++ b/BE/musicspot/src/journey/controller/journey.controller.ts @@ -36,6 +36,14 @@ import { DeleteJourneyReqDTO } from '../dto/journeyDelete.dto'; import { Journey } from '../entities/journey.entity'; import { LastJourneyResDTO } from '../dto/journeyLast.dto'; +import { + StartJourneyReqDTOV2, + StartJourneyResDTOV2, +} from '../dto/v2/startJourney.v2.dto'; +import { + EndJourneyReqDTOV2, + EndJourneyResDTOV2, +} from '../dto/v2/endJourney.v2.dto'; @Controller('journey') @ApiTags('journey 관련 API') @@ -50,18 +58,25 @@ export class JourneyController { description: '생성된 여정 데이터를 반환', type: StartJourneyResDTO, }) - @Post('start') + @Post('/start') async create(@Body() startJourneyDTO: StartJourneyReqDTO) { return await this.journeyService.insertJourneyData(startJourneyDTO); } + + @Version('2') @ApiOperation({ - summary: '여정 시작 API', + summary: '여정 시작 API(V2)', description: '여정 기록을 시작합니다.', }) @ApiCreatedResponse({ description: '생성된 여정 데이터를 반환', - type: StartJourneyResDTO, + type: StartJourneyResDTOV2, }) + @Post('start') + async createV2(@Body() startJourneyDTO: StartJourneyReqDTOV2) { + return await this.journeyService.insertJourneyDataV2(startJourneyDTO); + } + @ApiOperation({ summary: '여정 종료 API', description: '여정을 종료합니다.', @@ -75,6 +90,20 @@ export class JourneyController { return await this.journeyService.end(endJourneyReqDTO); } + @Version('2') + @ApiOperation({ + summary: '여정 종료 API(V2)', + description: '여정을 종료합니다.', + }) + @ApiCreatedResponse({ + description: '여정 종료 정보 반환', + type: EndJourneyResDTOV2, + }) + @Post('end') + async endV2(@Body() endJourneyReqDTO: EndJourneyReqDTOV2) { + return await this.journeyService.endV2(endJourneyReqDTO); + } + @ApiOperation({ summary: '여정 좌표 기록API', description: '여정의 좌표를 기록합니다.', diff --git a/BE/musicspot/src/journey/service/journey.service.ts b/BE/musicspot/src/journey/service/journey.service.ts index 072869a..f46bd65 100644 --- a/BE/musicspot/src/journey/service/journey.service.ts +++ b/BE/musicspot/src/journey/service/journey.service.ts @@ -1,191 +1,283 @@ -import { Injectable } from "@nestjs/common"; -import { JourneyRepository } from "../repository/journey.repository"; -import { StartJourneyReqDTO, StartJourneyResDTO } from '../dto/journeyStart/journeyStart.dto'; +import { Injectable } from '@nestjs/common'; +import { JourneyRepository } from '../repository/journey.repository'; +import { + StartJourneyReqDTO, + StartJourneyResDTO, +} from '../dto/journeyStart/journeyStart.dto'; import { JourneyNotFoundException, coordinateNotCorrectException, } from '../../filters/journey.exception'; -import { EndJourneyReqDTO, EndJourneyResDTO } from '../dto/journeyEnd/journeyEnd.dto'; -import { RecordJourneyReqDTO, RecordJourneyResDTO } from '../dto/journeyRecord/journeyRecord.dto'; -import { is1DArray, parseCoordinateFromGeoToDto, parseCoordinatesFromGeoToDto } from 'src/common/util/coordinate.util'; +import { + EndJourneyReqDTO, + EndJourneyResDTO, +} from '../dto/journeyEnd/journeyEnd.dto'; +import { + RecordJourneyReqDTO, + RecordJourneyResDTO, +} from '../dto/journeyRecord/journeyRecord.dto'; +import { + is1DArray, + parseCoordinateFromGeoToDto, + parseCoordinatesFromGeoToDto, +} from 'src/common/util/coordinate.util'; import { DeleteJourneyReqDTO } from '../dto/journeyDelete.dto'; import { UserRepository } from 'src/user/repository/user.repository'; import { Journey } from '../entities/journey.entity'; -import { makePresignedUrl } from "src/common/s3/objectStorage"; -import { parse } from "path"; +import { makePresignedUrl } from 'src/common/s3/objectStorage'; +import { parse } from 'path'; +import { + StartJourneyReqDTOV2, + StartJourneyResDTOV2, +} from '../dto/v2/startJourney.v2.dto'; +import { EndJourneyReqDTOV2 } from '../dto/v2/endJourney.v2.dto'; @Injectable() -export class JourneyService{ - constructor(private journeyRepository: JourneyRepository, private userRepository: UserRepository){} - - async insertJourneyData(startJourneyDTO:StartJourneyReqDTO){ - const startPoint = startJourneyDTO.coordinate.join(' '); - const lineStringOfCoordinates = `LINESTRING(${startPoint}, ${startPoint})` - - const returnedData = await this.journeyRepository.save({...startJourneyDTO, coordinates: lineStringOfCoordinates}) - - const [parsedCoordinate] = parseCoordinatesFromGeoToDto(returnedData.coordinates) - - const returnData:StartJourneyResDTO = { - journeyId : returnedData.journeyId, - coordinate : parsedCoordinate, - startTimestamp : returnedData.startTimestamp, - } - - return returnData; +export class JourneyService { + constructor( + private journeyRepository: JourneyRepository, + private userRepository: UserRepository, + ) {} + + async insertJourneyData(startJourneyDTO: StartJourneyReqDTO) { + const startPoint = startJourneyDTO.coordinate.join(' '); + const lineStringOfCoordinates = `LINESTRING(${startPoint}, ${startPoint})`; + + const returnedData = await this.journeyRepository.save({ + ...startJourneyDTO, + coordinates: lineStringOfCoordinates, + }); + + const [parsedCoordinate] = parseCoordinatesFromGeoToDto( + returnedData.coordinates, + ); + + const returnData: StartJourneyResDTO = { + journeyId: returnedData.journeyId, + coordinate: parsedCoordinate, + startTimestamp: returnedData.startTimestamp, + }; + + return returnData; + } + + async insertJourneyDataV2(startJourneyDTO: StartJourneyReqDTOV2) { + const returnedData = await this.journeyRepository.save(startJourneyDTO); + + const returnData: StartJourneyResDTOV2 = { + journeyId: returnedData.journeyId, + startTimestamp: returnedData.startTimestamp, + }; + + return returnData; + } + + async end(endJourneyDTO: EndJourneyReqDTO) { + const { coordinates, journeyId, song, title, endTimestamp } = endJourneyDTO; + const coordinatesLen = coordinates.length; + const originData = await this.journeyRepository.findOne({ + where: { journeyId }, + }); + if (!originData) { + throw new JourneyNotFoundException(); } - async end(endJourneyDTO: EndJourneyReqDTO){ - const {coordinates, journeyId, song, title, endTimestamp} = endJourneyDTO; - const coordinatesLen = coordinates.length; - const originData = await this.journeyRepository.findOne({where:{journeyId}}) - if(!originData){ - throw new JourneyNotFoundException(); - } - - - const originCoordinates =originData.coordinates; - const newCoordinates = originData.coordinates = originCoordinates.slice(0, -1) + ',' +endJourneyDTO.coordinates.map((item)=> `${item[0]} ${item[1]}`).join(',') + ')' - const newJourneyData = {...originData, ...endJourneyDTO, song : JSON.stringify(song), coordinates: newCoordinates}; - - const returnedDate = await this.journeyRepository.save(newJourneyData); - - const parsedCoordinates = parseCoordinatesFromGeoToDto(returnedDate.coordinates) - const returnData:EndJourneyResDTO = { - journeyId : returnedDate.journeyId, - coordinates : parsedCoordinates.slice(parsedCoordinates.length-coordinatesLen), - endTimestamp : returnedDate.endTimestamp, - numberOfCoordinates : parsedCoordinates.length, - song : JSON.parse(returnedDate.song) - } - - return returnData + const originCoordinates = originData.coordinates; + const newCoordinates = (originData.coordinates = + originCoordinates.slice(0, -1) + + ',' + + endJourneyDTO.coordinates + .map((item) => `${item[0]} ${item[1]}`) + .join(',') + + ')'); + const newJourneyData = { + ...originData, + ...endJourneyDTO, + song: JSON.stringify(song), + coordinates: newCoordinates, + }; + + const returnedDate = await this.journeyRepository.save(newJourneyData); + + const parsedCoordinates = parseCoordinatesFromGeoToDto( + returnedDate.coordinates, + ); + const returnData: EndJourneyResDTO = { + journeyId: returnedDate.journeyId, + coordinates: parsedCoordinates.slice( + parsedCoordinates.length - coordinatesLen, + ), + endTimestamp: returnedDate.endTimestamp, + numberOfCoordinates: parsedCoordinates.length, + song: JSON.parse(returnedDate.song), + }; + + return returnData; + } + async endV2(endJourneyDTO: EndJourneyReqDTOV2) { + const { coordinates, journeyId, song } = endJourneyDTO; + const coordinatesLen: number = coordinates.split(',').length; + const originalData = await this.journeyRepository.findOne({ + where: { journeyId }, + }); + if (!originalData) { + throw new JourneyNotFoundException(); } + const newCoordinates = `LINESTRING(${coordinates})`; + const newJourneyData = { + ...originalData, + ...endJourneyDTO, + song: JSON.stringify(song), + coordinates: newCoordinates, + }; + + const returnedDate = await this.journeyRepository.save(newJourneyData); + + const parsedCoordinates = parseCoordinatesFromGeoToDto( + returnedDate.coordinates, + ); + const returnData: EndJourneyResDTO = { + journeyId: returnedDate.journeyId, + coordinates: parsedCoordinates.slice( + parsedCoordinates.length - coordinatesLen, + ), + endTimestamp: returnedDate.endTimestamp, + numberOfCoordinates: parsedCoordinates.length, + song: JSON.parse(returnedDate.song), + }; + + return returnData; + } + async pushCoordianteToJourney(recordJourneyDTO: RecordJourneyReqDTO) { + const { journeyId, coordinates } = recordJourneyDTO; + const coordinateLen = coordinates.length; + const originData = await this.journeyRepository.findOne({ + where: { journeyId }, + }); + if (!originData) { + throw new JourneyNotFoundException(); + } + const originCoordinates = originData.coordinates; + + originData.coordinates = + originCoordinates.slice(0, -1) + + ',' + + recordJourneyDTO.coordinates + .map((item) => `${item[0]} ${item[1]}`) + .join(',') + + ')'; + const returnedData = await this.journeyRepository.save(originData); + + const updatedCoordinate = parseCoordinatesFromGeoToDto( + returnedData.coordinates, + ); + const len = updatedCoordinate.length; + + return { coordinates: updatedCoordinate.slice(len - coordinateLen) }; + } + + async getJourneyByCoordinationRange(checkJourneyDTO) { + let { userId, minCoordinate, maxCoordinate } = checkJourneyDTO; + if (!(Array.isArray(minCoordinate) && Array.isArray(maxCoordinate))) { + throw new coordinateNotCorrectException(); + } - async pushCoordianteToJourney(recordJourneyDTO: RecordJourneyReqDTO) { - - const {journeyId, coordinates} = recordJourneyDTO; - const coordinateLen = coordinates.length; - const originData = await this.journeyRepository.findOne({where:{journeyId}}); - if(!originData){ - throw new JourneyNotFoundException(); - } - const originCoordinates =originData.coordinates; - - - originData.coordinates = originCoordinates.slice(0, -1) + ',' +recordJourneyDTO.coordinates.map((item)=> `${item[0]} ${item[1]}`).join(',') + ')' - const returnedData = await this.journeyRepository.save(originData) - - const updatedCoordinate = parseCoordinatesFromGeoToDto(returnedData.coordinates); - const len = updatedCoordinate.length; + minCoordinate = minCoordinate.map((item) => Number(item)); + maxCoordinate = maxCoordinate.map((item) => Number(item)); - - return {coordinates:updatedCoordinate.slice(len-coordinateLen)} - + if (!(is1DArray(minCoordinate) && is1DArray(maxCoordinate))) { + throw new coordinateNotCorrectException(); + } + const coordinatesRange = { + xMinCoordinate: minCoordinate[0], + yMinCoordinate: minCoordinate[1], + xMaxCoordinate: maxCoordinate[0], + yMaxCoordinate: maxCoordinate[1], + }; + const returnedData = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .leftJoinAndSelect('journey.spots', 'spot') + .where( + `st_within(coordinates, ST_PolygonFromText('POLYGON((:xMinCoordinate :yMinCoordinate, :xMaxCoordinate :yMinCoordinate, :xMaxCoordinate :yMaxCoordinate, :xMinCoordinate :yMaxCoordinate, :xMinCoordinate :yMinCoordinate))'))`, + coordinatesRange, + ) + .where('userId = :userId', { userId }) + .getMany(); + + return returnedData.map((data) => { + return this.parseJourneyFromEntityToDto(data); + }); + } + + async getLastJourneyByUserId(userId) { + const journeys = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .where({ userId }) + .leftJoinAndSelect('journey.spots', 'spot') + .getMany(); + + if (!journeys) { + return { + journey: null, + isRecording: false, + }; } + const journeyLen = journeys.length; + const lastJourneyData = journeys[journeyLen - 1]; - - async getJourneyByCoordinationRange(checkJourneyDTO) { - let { userId, minCoordinate, maxCoordinate } = checkJourneyDTO; - if (!(Array.isArray(minCoordinate) && Array.isArray(maxCoordinate))) { - throw new coordinateNotCorrectException(); - } - - minCoordinate = minCoordinate.map((item) => Number(item)); - maxCoordinate = maxCoordinate.map((item) => Number(item)); - - if (!(is1DArray(minCoordinate) && is1DArray(maxCoordinate))) { - throw new coordinateNotCorrectException(); - } - const coordinatesRange = { - xMinCoordinate : minCoordinate[0], - yMinCoordinate : minCoordinate[1], - xMaxCoordinate : maxCoordinate[0], - yMaxCoordinate : maxCoordinate[1] - } - const returnedData = await this.journeyRepository.manager - .createQueryBuilder(Journey, "journey") - .leftJoinAndSelect("journey.spots", "spot") - .where(`st_within(coordinates, ST_PolygonFromText('POLYGON((:xMinCoordinate :yMinCoordinate, :xMaxCoordinate :yMinCoordinate, :xMaxCoordinate :yMaxCoordinate, :xMinCoordinate :yMaxCoordinate, :xMinCoordinate :yMinCoordinate))'))`, coordinatesRange) - .where('userId = :userId', {userId}) - .getMany(); - - return returnedData.map(data =>{ - return this.parseJourneyFromEntityToDto(data) - }) + if (lastJourneyData.title) { + return { journey: null, isRecording: false }; } - async getLastJourneyByUserId(userId){ - const journeys = await this.journeyRepository.manager - .createQueryBuilder(Journey, "journey") - .where({userId}) - .leftJoinAndSelect("journey.spots", "spot") - .getMany() - - if(!journeys){ + return { + journey: this.parseJourneyFromEntityToDto(lastJourneyData), + isRecording: true, + }; + } + + async getJourneyById(journeyId) { + const returnedData = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .where({ journeyId }) + .leftJoinAndSelect('journey.spots', 'spot') + .getOne(); + return this.parseJourneyFromEntityToDto(returnedData); + } + + parseJourneyFromEntityToDto(journey) { + const { + journeyId, + coordinates, + startTimestamp, + endTimestamp, + song, + title, + spots, + } = journey; + return { + journeyId, + coordinates: parseCoordinatesFromGeoToDto(coordinates), + title, + journeyMetadata: { + startTimestamp: journey.startTimestamp, + endTimestamp: journey.endTimestamp, + }, + song: JSON.parse(song), + spots: journey.spots.map((spot) => { return { - journey : null, - isRecording: false + ...spot, + coordinate: parseCoordinateFromGeoToDto(spot.coordinate), + photoUrl: makePresignedUrl(spot.photoKey), }; - } - - const journeyLen = journeys.length; - const lastJourneyData = journeys[journeyLen-1]; - - if(lastJourneyData.title){ - return {journey:null, isRecording : false} - } - - - return { - journey : this.parseJourneyFromEntityToDto(lastJourneyData), - isRecording : true - - - } - - - } - - async getJourneyById(journeyId) { - const returnedData = await this.journeyRepository.manager - .createQueryBuilder(Journey, "journey") - .where({journeyId}) - .leftJoinAndSelect("journey.spots", "spot") - .getOne() - return this.parseJourneyFromEntityToDto(returnedData); - - - } - - parseJourneyFromEntityToDto(journey){ - const {journeyId, coordinates, startTimestamp, endTimestamp, song, title, spots} = journey; - return { - journeyId, - coordinates : parseCoordinatesFromGeoToDto(coordinates), - title, - journeyMetadata : {startTimestamp : journey.startTimestamp, endTimestamp : journey.endTimestamp}, - song : JSON.parse(song), - spots : journey.spots.map(spot =>{ - return { - ...spot, - coordinate : parseCoordinateFromGeoToDto(spot.coordinate), - photoUrl : makePresignedUrl(spot.photoKey) - } - }) - - } - } - - async deleteJourneyById(deletedJourneyDto: DeleteJourneyReqDTO){ - const {journeyId} = deletedJourneyDto; - return await this.journeyRepository.delete({journeyId}) - } - + }), + }; + } + + async deleteJourneyById(deletedJourneyDto: DeleteJourneyReqDTO) { + const { journeyId } = deletedJourneyDto; + return await this.journeyRepository.delete({ journeyId }); + } } - - - From f1afbb442e03f51266a0e68e2c1b02cbe45dd8b6 Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 23:17:10 +0900 Subject: [PATCH 5/7] =?UTF-8?q?:recycle:=20(V2)=20coordinates=20geo->dto?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (가 포함되어 parse되던 문제 해결 --- BE/musicspot/src/common/util/coordinate.v2.util.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BE/musicspot/src/common/util/coordinate.v2.util.ts b/BE/musicspot/src/common/util/coordinate.v2.util.ts index 3734a74..3ef3cc3 100644 --- a/BE/musicspot/src/common/util/coordinate.v2.util.ts +++ b/BE/musicspot/src/common/util/coordinate.v2.util.ts @@ -12,7 +12,6 @@ export const isPointString = (pointString: string): boolean => { export const isLinestring = (lineString: string): boolean => { const regex: RegExp = /^\d+.\d+\s\d+.\d+,(\d+.\d+\s\d+.\d+,)*\d+.\d+\s\d+.\d+$/; - console.log(':ASD'); if (!lineString.match(regex)) { return false; } @@ -57,6 +56,6 @@ export const parseCoordinatesFromDtoToGeoV2 = (coordinates: string): string => { export const parseCoordinatesFromGeoToDtoV2 = (coordinates: string): string => { // coordinates = 'LINESTRING(1 2,3 4)' - const pointLen = 'LINESTRING'.length; + const pointLen = 'LINESTRING('.length; return coordinates.slice(pointLen, -1); }; From c13c9f2a6cf1edca2860af1d6dc9293e46b4ced8 Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Fri, 9 Feb 2024 23:19:01 +0900 Subject: [PATCH 6/7] =?UTF-8?q?:sparkles:=20(V2)=20coordinate=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84=EC=97=90=20=ED=8F=AC=ED=95=A8=EB=90=98=EB=8A=94=20jou?= =?UTF-8?q?rney=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit coordinate 형식 수정에 따른 get 로직 수정 --- .../journey/controller/journey.controller.ts | 71 ++++++++++ .../src/journey/service/journey.service.ts | 121 ++++++++++++++++-- 2 files changed, 181 insertions(+), 11 deletions(-) diff --git a/BE/musicspot/src/journey/controller/journey.controller.ts b/BE/musicspot/src/journey/controller/journey.controller.ts index e069871..fcb88bf 100644 --- a/BE/musicspot/src/journey/controller/journey.controller.ts +++ b/BE/musicspot/src/journey/controller/journey.controller.ts @@ -119,6 +119,50 @@ export class JourneyController { return returnData; } + @Version('2') + @ApiOperation({ + summary: '여정 조회 API', + description: '해당 범위 내의 여정들을 반환합니다.', + }) + @ApiQuery({ + name: 'userId', + description: '유저 ID', + required: true, + example: 'yourUserId', + }) + @ApiQuery({ + name: 'minCoordinate', + description: '최소 좌표', + required: true, + example: '37.5 127.0', + }) + @ApiQuery({ + name: 'maxCoordinate', + description: '최대 좌표', + required: true, + example: '38.0 128.0', + }) + @ApiCreatedResponse({ + description: '범위에 있는 여정의 기록들을 반환', + type: CheckJourneyResDTO, + }) + @Get() + @UsePipes(ValidationPipe) + async getJourneyByCoordinate( + @Query('userId') userId: UUID, + @Query('minCoordinate') minCoordinate: string, + @Query('maxCoordinate') maxCoordinate: string, + ) { + console.log('min:', minCoordinate, 'max:', maxCoordinate); + const checkJourneyDTO = { + userId, + minCoordinate, + maxCoordinate, + }; + return await this.journeyService.getJourneyByCoordinationRangeV2( + checkJourneyDTO, + ); + } @ApiOperation({ summary: '여정 조회 API', description: '해당 범위 내의 여정들을 반환합니다.', @@ -167,6 +211,19 @@ export class JourneyController { ); } + @Version('2') + @ApiOperation({ + summary: '최근 여정 조회 API', + description: '진행 중인 여정이 있었는 지 확인', + }) + @ApiCreatedResponse({ + description: '사용자가 진행중이었던 여정 정보', + type: LastJourneyResDTO, + }) + @Get('last') + async loadLastDataV2(@Body('userId') userId) { + return await this.journeyService.getLastJourneyByUserIdV2(userId); + } @ApiOperation({ summary: '최근 여정 조회 API', description: '진행 중인 여정이 있었는 지 확인', @@ -180,6 +237,20 @@ export class JourneyController { return await this.journeyService.getLastJourneyByUserId(userId); } + @Version('2') + @ApiOperation({ + summary: '여정 조회 API', + description: 'journey id를 통해 여정을 조회', + }) + @ApiCreatedResponse({ + description: 'journey id에 해당하는 여정을 반환', + type: [Journey], + }) + @Get(':journeyId') + async getJourneyByIdV2(@Param('journeyId') journeyId: string) { + return await this.journeyService.getJourneyByIdV2(journeyId); + } + @ApiOperation({ summary: '여정 조회 API', description: 'journey id를 통해 여정을 조회', diff --git a/BE/musicspot/src/journey/service/journey.service.ts b/BE/musicspot/src/journey/service/journey.service.ts index f46bd65..b0911da 100644 --- a/BE/musicspot/src/journey/service/journey.service.ts +++ b/BE/musicspot/src/journey/service/journey.service.ts @@ -32,6 +32,12 @@ import { StartJourneyResDTOV2, } from '../dto/v2/startJourney.v2.dto'; import { EndJourneyReqDTOV2 } from '../dto/v2/endJourney.v2.dto'; +import { + isPointString, + parseCoordinateFromGeoToDtoV2, + parseCoordinatesFromGeoToDtoV2, +} from '../../common/util/coordinate.v2.util'; +import { UserNotFoundException } from '../../filters/user.exception'; @Injectable() export class JourneyService { @@ -117,7 +123,6 @@ export class JourneyService { } async endV2(endJourneyDTO: EndJourneyReqDTOV2) { const { coordinates, journeyId, song } = endJourneyDTO; - const coordinatesLen: number = coordinates.split(',').length; const originalData = await this.journeyRepository.findOne({ where: { journeyId }, }); @@ -135,14 +140,12 @@ export class JourneyService { const returnedDate = await this.journeyRepository.save(newJourneyData); - const parsedCoordinates = parseCoordinatesFromGeoToDto( + const parsedCoordinates = parseCoordinatesFromGeoToDtoV2( returnedDate.coordinates, ); - const returnData: EndJourneyResDTO = { + const returnData = { journeyId: returnedDate.journeyId, - coordinates: parsedCoordinates.slice( - parsedCoordinates.length - coordinatesLen, - ), + coordinates: parsedCoordinates, endTimestamp: returnedDate.endTimestamp, numberOfCoordinates: parsedCoordinates.length, song: JSON.parse(returnedDate.song), @@ -178,6 +181,43 @@ export class JourneyService { return { coordinates: updatedCoordinate.slice(len - coordinateLen) }; } + async getJourneyByCoordinationRangeV2(checkJourneyDTO) { + const { userId, minCoordinate, maxCoordinate } = checkJourneyDTO; + if (!(await this.userRepository.findOne({ where: { userId } }))) { + throw new UserNotFoundException(); + } + + if (!(isPointString(minCoordinate) && isPointString(maxCoordinate))) { + throw new coordinateNotCorrectException(); + } + + const [xMinCoordinate, yMinCoordinate] = minCoordinate + .split(' ') + .map((str) => Number(str)); + const [xMaxCoordinate, yMaxCoordinate] = maxCoordinate + .split(' ') + .map((str) => Number(str)); + console.log(xMinCoordinate, yMinCoordinate, xMaxCoordinate, yMaxCoordinate); + const coordinatesRange = { + xMinCoordinate, + yMinCoordinate, + xMaxCoordinate, + yMaxCoordinate, + }; + const returnedData = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .leftJoinAndSelect('journey.spots', 'spot') + .where( + `st_within(coordinates, ST_PolygonFromText('POLYGON((:xMinCoordinate :yMinCoordinate, :xMaxCoordinate :yMinCoordinate, :xMaxCoordinate :yMaxCoordinate, :xMinCoordinate :yMaxCoordinate, :xMinCoordinate :yMinCoordinate))'))`, + coordinatesRange, + ) + .where('userId = :userId', { userId }) + .getMany(); + console.log(returnedData); + return returnedData.map((data) => { + return this.parseJourneyFromEntityToDtoV2(data); + }); + } async getJourneyByCoordinationRange(checkJourneyDTO) { let { userId, minCoordinate, maxCoordinate } = checkJourneyDTO; if (!(Array.isArray(minCoordinate) && Array.isArray(maxCoordinate))) { @@ -210,7 +250,32 @@ export class JourneyService { return this.parseJourneyFromEntityToDto(data); }); } + async getLastJourneyByUserIdV2(userId) { + const journeys = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .where({ userId }) + .leftJoinAndSelect('journey.spots', 'spot') + .getMany(); + + if (!journeys) { + return { + journey: null, + isRecording: false, + }; + } + + const journeyLen = journeys.length; + const lastJourneyData = journeys[journeyLen - 1]; + + if (lastJourneyData.title) { + return { journey: null, isRecording: false }; + } + return { + journey: this.parseJourneyFromEntityToDtoV2(lastJourneyData), + isRecording: true, + }; + } async getLastJourneyByUserId(userId) { const journeys = await this.journeyRepository.manager .createQueryBuilder(Journey, 'journey') @@ -237,7 +302,14 @@ export class JourneyService { isRecording: true, }; } - + async getJourneyByIdV2(journeyId) { + const returnedData = await this.journeyRepository.manager + .createQueryBuilder(Journey, 'journey') + .where({ journeyId }) + .leftJoinAndSelect('journey.spots', 'spot') + .getOne(); + return this.parseJourneyFromEntityToDtoV2(returnedData); + } async getJourneyById(journeyId) { const returnedData = await this.journeyRepository.manager .createQueryBuilder(Journey, 'journey') @@ -246,7 +318,34 @@ export class JourneyService { .getOne(); return this.parseJourneyFromEntityToDto(returnedData); } - + parseJourneyFromEntityToDtoV2(journey) { + const { + journeyId, + coordinates, + startTimestamp, + endTimestamp, + song, + title, + spots, + } = journey; + return { + journeyId, + coordinates: parseCoordinatesFromGeoToDtoV2(coordinates), + title, + journeyMetadata: { + startTimestamp, + endTimestamp, + }, + song: JSON.parse(song), + spots: spots.map((spot) => { + return { + ...spot, + coordinate: parseCoordinateFromGeoToDtoV2(spot.coordinate), + photoUrl: makePresignedUrl(spot.photoKey), + }; + }), + }; + } parseJourneyFromEntityToDto(journey) { const { journeyId, @@ -262,11 +361,11 @@ export class JourneyService { coordinates: parseCoordinatesFromGeoToDto(coordinates), title, journeyMetadata: { - startTimestamp: journey.startTimestamp, - endTimestamp: journey.endTimestamp, + startTimestamp, + endTimestamp, }, song: JSON.parse(song), - spots: journey.spots.map((spot) => { + spots: spots.map((spot) => { return { ...spot, coordinate: parseCoordinateFromGeoToDto(spot.coordinate), From 2751a069e3654d651e6f3fdb1a4580590853926d Mon Sep 17 00:00:00 2001 From: twoo1999 Date: Sat, 10 Feb 2024 16:58:04 +0900 Subject: [PATCH 7/7] =?UTF-8?q?:sparkles:=20(V2)=20coordinate=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?spot=20dto=20=EB=B0=8F=20=EB=B9=84=EC=A7=80=EB=8B=88=EC=8A=A4?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/journeyCheck/journeyCheck.dto.ts | 2 +- .../src/journey/service/journey.service.ts | 2 +- .../src/spot/controller/spot.controller.ts | 19 ++ .../src/spot/dto/v2/recordSpot.v2.dto.ts | 63 +++++ BE/musicspot/src/spot/service/spot.service.ts | 219 +++++++++++------- 5 files changed, 221 insertions(+), 84 deletions(-) create mode 100644 BE/musicspot/src/spot/dto/v2/recordSpot.v2.dto.ts diff --git a/BE/musicspot/src/journey/dto/journeyCheck/journeyCheck.dto.ts b/BE/musicspot/src/journey/dto/journeyCheck/journeyCheck.dto.ts index a8e76f1..d34cd9e 100644 --- a/BE/musicspot/src/journey/dto/journeyCheck/journeyCheck.dto.ts +++ b/BE/musicspot/src/journey/dto/journeyCheck/journeyCheck.dto.ts @@ -42,7 +42,7 @@ export class SpotDTO { @ApiProperty({ description: '여정 ID', example: '65649c91380cafcab8869ed2' }) readonly journeyId: string; - @ApiProperty({ description: 'spot 위치', example: [37.555913, 126.972313] }) + @ApiProperty({ description: 'spot 위치', example: '37.555913 126.972313' }) readonly coordinate: number[]; @ApiProperty({ description: '기록 시간', example: '2023-11-22T12:00:00Z' }) diff --git a/BE/musicspot/src/journey/service/journey.service.ts b/BE/musicspot/src/journey/service/journey.service.ts index b0911da..bc71e10 100644 --- a/BE/musicspot/src/journey/service/journey.service.ts +++ b/BE/musicspot/src/journey/service/journey.service.ts @@ -272,7 +272,7 @@ export class JourneyService { } return { - journey: this.parseJourneyFromEntityToDtoV2(lastJourneyData), + journey: lastJourneyData, isRecording: true, }; } diff --git a/BE/musicspot/src/spot/controller/spot.controller.ts b/BE/musicspot/src/spot/controller/spot.controller.ts index 8c13db5..830a96d 100644 --- a/BE/musicspot/src/spot/controller/spot.controller.ts +++ b/BE/musicspot/src/spot/controller/spot.controller.ts @@ -6,6 +6,7 @@ import { UploadedFile, Get, Query, + Version, } from '@nestjs/common'; import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { RecordSpotReqDTO } from '../dto/recordSpot.dto'; @@ -13,6 +14,7 @@ import { SpotService } from '../service/spot.service'; import { FileInterceptor } from '@nestjs/platform-express'; import { Spot } from '../schema/spot.schema'; import { SpotDTO } from 'src/journey/dto/journeyCheck/journeyCheck.dto'; +import {RecordSpotReqDTOV2} from "../dto/v2/recordSpot.v2.dto"; @Controller('spot') @ApiTags('spot 관련 API') export class SpotController { @@ -35,6 +37,23 @@ export class SpotController { return await this.spotService.create(file, recordSpotDTO); } + @Version('2') + @ApiOperation({ + summary: 'spot 기록 API', + description: 'spot을 기록합니다.', + }) + @ApiCreatedResponse({ + description: 'spot 생성 데이터 반환', + type: SpotDTO, + }) + @UseInterceptors(FileInterceptor('image')) + @Post('') + async createV2( + @UploadedFile() file: Express.Multer.File, + @Body() recordSpotDTO, + ) { + return await this.spotService.createV2(file, recordSpotDTO); + } @ApiOperation({ summary: 'spot 조회 API', description: 'spotId로 스팟 이미지를 조회합니다.', diff --git a/BE/musicspot/src/spot/dto/v2/recordSpot.v2.dto.ts b/BE/musicspot/src/spot/dto/v2/recordSpot.v2.dto.ts new file mode 100644 index 0000000..a506726 --- /dev/null +++ b/BE/musicspot/src/spot/dto/v2/recordSpot.v2.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsNumber } from 'class-validator'; +import { IsCoordinateV2 } from '../../../common/decorator/coordinate.v2.decorator'; + +export class RecordSpotReqDTOV2 { + @ApiProperty({ + example: '20', + description: '여정 id', + required: true, + }) + @IsNumber() + readonly journeyId: number; + + @ApiProperty({ + example: '37.555946 126.972384', + description: '위치 좌표', + required: true, + }) + @IsCoordinateV2({ + message: + '위치 좌표는 2개의 숫자와 각각의 범위를 만족해야합니다.(-90~90 , -180~180)', + }) + readonly coordinate: string; + + @ApiProperty({ + example: '2023-11-22T12:00:00Z', + description: 'timestamp', + required: true, + }) + @IsDateString() + readonly timestamp: string; +} + +export class RecordSpotResDTOV2 { + @ApiProperty({ + example: 20, + description: '여정 id', + required: true, + }) + readonly journeyId: number; + + @ApiProperty({ + example: '37.555946 126.972384', + description: '위치 좌표', + required: true, + }) + readonly coordinate: string; + + @ApiProperty({ + example: '2023-11-22T12:00:00Z', + description: 'timestamp', + required: true, + }) + readonly timestamp: string; + + @ApiProperty({ + example: + 'https://music-spot-storage.kr.object.ncloudstorage.com/path/name?AWSAccessKeyId=key&Expires=1701850233&Signature=signature', + description: 'presigned url', + required: true, + }) + readonly photoUrl: string; +} diff --git a/BE/musicspot/src/spot/service/spot.service.ts b/BE/musicspot/src/spot/service/spot.service.ts index f556a5c..69bf732 100644 --- a/BE/musicspot/src/spot/service/spot.service.ts +++ b/BE/musicspot/src/spot/service/spot.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import {Spot} from '../entities/spot.entity' +import { Spot } from '../entities/spot.entity'; import { SpotNotFoundException, SpotRecordFail, @@ -10,99 +10,154 @@ import { makePresignedUrl, } from '../../common/s3/objectStorage'; -import { JourneyNotFoundException, coordinateNotCorrectException } from 'src/filters/journey.exception'; -import { is1DArray, parseCoordinateFromDtoToGeo } from 'src/common/util/coordinate.util'; +import { + JourneyNotFoundException, + coordinateNotCorrectException, +} from 'src/filters/journey.exception'; +import { + is1DArray, + parseCoordinateFromDtoToGeo, parseCoordinateFromGeoToDto, +} from 'src/common/util/coordinate.util'; import { SpotRepository } from '../repository/spot.repository'; import { RecordSpotResDTO } from '../dto/recordSpot.dto'; import { JourneyRepository } from 'src/journey/repository/journey.repository'; +import { + RecordSpotReqDTOV2, + RecordSpotResDTOV2, +} from '../dto/v2/recordSpot.v2.dto'; +import { + isPointString, + parseCoordinateFromDtoToGeoV2, parseCoordinateFromGeoToDtoV2, +} from '../../common/util/coordinate.v2.util'; @Injectable() export class SpotService { constructor( - private spotRepository: SpotRepository, private journeyRepository: JourneyRepository) {} - - - async uploadPhotoToStorage(journeyId, file) { - try{ - const key = `${journeyId}/${Date.now()}`; - const result = await S3.putObject({ - Bucket: bucketName, - Key: key, - Body: file.buffer, - }).promise(); - - return key; - } catch(err){ - throw new SpotRecordFail(); - } - } + private spotRepository: SpotRepository, + private journeyRepository: JourneyRepository, + ) {} + + async uploadPhotoToStorage(journeyId, file) { + try { + const key = `${journeyId}/${Date.now()}`; + const result = await S3.putObject({ + Bucket: bucketName, + Key: key, + Body: file.buffer, + }).promise(); - - async insertToSpot(spotData){ - const point = `POINT(${parseCoordinateFromDtoToGeo(spotData.coordinate)})`; - const data = {...spotData, journeyId :Number(spotData.journeyId), coordinate: point } - - return await this.spotRepository.save(data); + return key; + } catch (err) { + throw new SpotRecordFail(); } - - async updateCoordinatesToJourney(journeyId, coordinate){ - const parsedCoordinate = parseCoordinateFromDtoToGeo(coordinate); - const originalJourney = await this.journeyRepository.findOne({where:{journeyId}}) - const lineStringLen = 'LINESTRING('.length; - - if(!originalJourney){ - throw new JourneyNotFoundException(); - } - originalJourney.coordinates = `LINESTRING(${originalJourney.coordinates.slice(lineStringLen, -1)}, ${parsedCoordinate})` - - - return await this.journeyRepository.save(originalJourney); + } + + async insertToSpotV2(spotData) { + const data = { + ...spotData, + journeyId: Number(spotData.journeyId), + }; + + return await this.spotRepository.save(data); + } + async insertToSpot(spotData) { + const point = `POINT(${parseCoordinateFromDtoToGeo(spotData.coordinate)})`; + const data = { + ...spotData, + journeyId: Number(spotData.journeyId), + coordinate: point, + }; + + return await this.spotRepository.save(data); + } + + async updateCoordinatesToJourney(journeyId, coordinate) { + const parsedCoordinate = parseCoordinateFromDtoToGeo(coordinate); + const originalJourney = await this.journeyRepository.findOne({ + where: { journeyId }, + }); + const lineStringLen = 'LINESTRING('.length; + + if (!originalJourney) { + throw new JourneyNotFoundException(); } - - async create(file, recordSpotDto){ - let parsedCoordinate; - try { - parsedCoordinate = JSON.parse(recordSpotDto.coordinate); - } catch (err) { - throw new coordinateNotCorrectException(); - } - if (!is1DArray(parsedCoordinate)) { - throw new coordinateNotCorrectException(); - } - - const photoKey = await this.uploadPhotoToStorage( - recordSpotDto.journeyId, - file, - ); - const presignedUrl = makePresignedUrl(photoKey); - - const createdSpotData = await this.insertToSpot({ - ...recordSpotDto, - photoKey, - coordinate: parsedCoordinate - }); - const updatedJourneyData = await this.updateCoordinatesToJourney(recordSpotDto.journeyId, parsedCoordinate) - - const returnData:RecordSpotResDTO = { - journeyId : createdSpotData.journeyId, - coordinate : parsedCoordinate, - timestamp : createdSpotData.timestamp, - photoUrl : presignedUrl - - } - - return returnData + originalJourney.coordinates = `LINESTRING(${originalJourney.coordinates.slice( + lineStringLen, + -1, + )}, ${parsedCoordinate})`; + + return await this.journeyRepository.save(originalJourney); + } + + async createV2(file, recordSpotDto) { + const { coordinate } = recordSpotDto; + if (!isPointString(coordinate)) { + throw new coordinateNotCorrectException(); } - - async getSpotImage(spotId: number) { - const spot = await this.spotRepository.findOne({where: {spotId}}); - if (!spot) { - throw new SpotNotFoundException(); - } - - return spot.photoKey; + const photoKey: string = await this.uploadPhotoToStorage( + recordSpotDto.journeyId, + file, + ); + const presignedUrl: string = makePresignedUrl(photoKey); + const createdSpotData = await this.insertToSpotV2({ + ...recordSpotDto, + photoKey, + coordinate: parseCoordinateFromDtoToGeoV2(coordinate), + }); + + const returnData: RecordSpotResDTOV2 = { + journeyId: createdSpotData.journeyId, + coordinate: parseCoordinateFromGeoToDtoV2(createdSpotData.coordinate), + timestamp: createdSpotData.timestamp, + photoUrl: presignedUrl, + }; + + return returnData; + } + async create(file, recordSpotDto) { + let parsedCoordinate; + try { + parsedCoordinate = JSON.parse(recordSpotDto.coordinate); + } catch (err) { + throw new coordinateNotCorrectException(); } -} + if (!is1DArray(parsedCoordinate)) { + throw new coordinateNotCorrectException(); + } + + const photoKey = await this.uploadPhotoToStorage( + recordSpotDto.journeyId, + file, + ); + const presignedUrl = makePresignedUrl(photoKey); + const createdSpotData = await this.insertToSpot({ + ...recordSpotDto, + photoKey, + coordinate: parsedCoordinate, + }); + const updatedJourneyData = await this.updateCoordinatesToJourney( + recordSpotDto.journeyId, + parsedCoordinate, + ); + const returnData: RecordSpotResDTO = { + journeyId: createdSpotData.journeyId, + coordinate: parsedCoordinate, + timestamp: createdSpotData.timestamp, + photoUrl: presignedUrl, + }; + + return returnData; + } + + async getSpotImage(spotId: number) { + const spot = await this.spotRepository.findOne({ where: { spotId } }); + if (!spot) { + throw new SpotNotFoundException(); + } + + return spot.photoKey; + } +}