Skip to content
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

[BE] ✨ : coordinate 형식 변경에 따른 spot dto 및 비지니스 로직 수정 #367

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down
2 changes: 1 addition & 1 deletion BE/musicspot/src/journey/service/journey.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ export class JourneyService {
}

return {
journey: this.parseJourneyFromEntityToDtoV2(lastJourneyData),
journey: lastJourneyData,
isRecording: true,
};
}
Expand Down
19 changes: 19 additions & 0 deletions BE/musicspot/src/spot/controller/spot.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
UploadedFile,
Get,
Query,
Version,
} from '@nestjs/common';
import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { RecordSpotReqDTO } from '../dto/recordSpot.dto';
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 {
Expand All @@ -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로 스팟 이미지를 조회합니다.',
Expand Down
63 changes: 63 additions & 0 deletions BE/musicspot/src/spot/dto/v2/recordSpot.v2.dto.ts
Original file line number Diff line number Diff line change
@@ -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;
}
219 changes: 137 additions & 82 deletions BE/musicspot/src/spot/service/spot.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import {Spot} from '../entities/spot.entity'
import { Spot } from '../entities/spot.entity';
import {
SpotNotFoundException,
SpotRecordFail,
Expand All @@ -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;
}
}