diff --git a/BE/musicspot/package-lock.json b/BE/musicspot/package-lock.json index 0421604..5279142 100644 --- a/BE/musicspot/package-lock.json +++ b/BE/musicspot/package-lock.json @@ -14,6 +14,8 @@ "@nestjs/mongoose": "^10.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.15", + "@types/multer": "^1.4.10", + "aws-sdk": "^2.348.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "gitmoji-cli": "^9.0.0", @@ -2035,7 +2037,6 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -2045,7 +2046,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -2086,7 +2086,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -2098,7 +2097,6 @@ "version": "4.17.41", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", - "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2123,8 +2121,7 @@ "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -2169,8 +2166,15 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/multer": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.10.tgz", + "integrity": "sha512-6l9mYMhUe8wbnz/67YIjc7ZJyQNZoKq7fRXVf7nMdgWgalD0KyzJ2ywI7hoATUSXSbTu9q2HBiEwzy0tNN1v2w==", + "dependencies": { + "@types/express": "*" + } }, "node_modules/@types/node": { "version": "20.9.0", @@ -2183,14 +2187,12 @@ "node_modules/@types/qs": { "version": "6.9.10", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", - "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", - "dev": true + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/semver": { "version": "7.5.5", @@ -2202,7 +2204,6 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -2212,7 +2213,6 @@ "version": "1.15.5", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", - "dev": true, "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -2869,6 +2869,58 @@ "when-exit": "^2.0.0" } }, + "node_modules/aws-sdk": { + "version": "2.348.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.348.0.tgz", + "integrity": "sha512-TfguapuOAwk7EG8zhYJPjkCaF4tyGjfgcXLkYbWbuS4O6E8pn0x2K5Yt1KXwLiWxG0fzKCLiiaNA5H7bKAP4YQ==", + "dependencies": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.1.0", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha512-DNK4ruAqtyHaN8Zne7PkBTO+dD1Lr0YfTduMqlIyjvQIoztBkUxrvL+hKeLW8NXFKHOq/2upkxuoS9znQ9bW9A==", + "deprecated": "This version of 'buffer' is out-of-date. You must update to v4.9.2 or newer", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/aws-sdk/node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/aws-sdk/node_modules/ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha512-/aoyv2Nt7mGLnCAWzE0C1WH9Xd8ZsqR0f4Pjwxputi1JNm01+InyAYQotF4N+ulEIjbEsJo22NOHr+U/XEZ1Pw==" + }, + "node_modules/aws-sdk/node_modules/uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -7407,6 +7459,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8770,6 +8830,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9213,6 +9282,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -10466,6 +10540,20 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10880,6 +10968,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/BE/musicspot/package.json b/BE/musicspot/package.json index 226d75a..0f6e83f 100644 --- a/BE/musicspot/package.json +++ b/BE/musicspot/package.json @@ -25,7 +25,8 @@ "@nestjs/mongoose": "^10.0.2", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.15", - + "@types/multer": "^1.4.10", + "aws-sdk": "^2.348.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "gitmoji-cli": "^9.0.0", diff --git a/BE/musicspot/src/app.module.ts b/BE/musicspot/src/app.module.ts index ee7887b..0368fd2 100644 --- a/BE/musicspot/src/app.module.ts +++ b/BE/musicspot/src/app.module.ts @@ -3,7 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MongooseModule } from '@nestjs/mongoose'; import { JourneyModule } from './journey/journey.module'; - +import { SpotModule } from './spot/spot.module'; import { UserModule } from './user/user.module'; @Module({ imports: [ @@ -12,6 +12,7 @@ import { UserModule } from './user/user.module'; ), JourneyModule, UserModule, + SpotModule, ], controllers: [AppController], providers: [AppService], diff --git a/BE/musicspot/src/journey/journey.controller.spec.ts b/BE/musicspot/src/journey/journey.controller.spec.ts index e428104..81b08ee 100644 --- a/BE/musicspot/src/journey/journey.controller.spec.ts +++ b/BE/musicspot/src/journey/journey.controller.spec.ts @@ -1,5 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { JourneyController } from './journey.controller'; + import { StartJourneyDTO } from './dto/journeyStart.dto'; import { JourneyService } from './journey.service'; import mongoose from 'mongoose'; @@ -29,6 +30,7 @@ describe('JourneyController', () => { useValue: userModel, }, ], + }).compile(); controller = module.get(JourneyController); @@ -52,5 +54,6 @@ describe('JourneyController', () => { afterAll(async () => { mongoose.connection.close(); + }); }); diff --git a/BE/musicspot/src/journey/journey.controller.ts b/BE/musicspot/src/journey/journey.controller.ts index 6d1c21c..1ef076f 100644 --- a/BE/musicspot/src/journey/journey.controller.ts +++ b/BE/musicspot/src/journey/journey.controller.ts @@ -4,6 +4,7 @@ import { StartJourneyDTO } from './dto/journeyStart.dto'; import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { Journey } from './journey.schema'; import { EndJourneyDTO } from './dto/journeyEnd.dto'; + @Controller('journey') @ApiTags('journey 관련 API') export class JourneyController { @@ -18,6 +19,7 @@ export class JourneyController { description: '생성된 여정 데이터를 반환', type: Journey, }) + @Post('start') async create(@Body() startJourneyDTO: StartJourneyDTO) { return await this.journeyService.create(startJourneyDTO); @@ -34,4 +36,5 @@ export class JourneyController { async end(@Body() endJourneyDTO: EndJourneyDTO) { return await this.journeyService.end(endJourneyDTO); } + } diff --git a/BE/musicspot/src/journey/journey.schema.ts b/BE/musicspot/src/journey/journey.schema.ts index 0d49553..6de7565 100644 --- a/BE/musicspot/src/journey/journey.schema.ts +++ b/BE/musicspot/src/journey/journey.schema.ts @@ -16,6 +16,7 @@ export class Journey { @Prop({ type: String }) timestamp: string; + } export const JourneySchema = SchemaFactory.createForClass(Journey); diff --git a/BE/musicspot/src/journey/journey.service.spec.ts b/BE/musicspot/src/journey/journey.service.spec.ts index a767b83..45a7ae3 100644 --- a/BE/musicspot/src/journey/journey.service.spec.ts +++ b/BE/musicspot/src/journey/journey.service.spec.ts @@ -11,6 +11,7 @@ describe('JourneysService', () => { let service: JourneyService; let userModel; let journeyModel; + beforeAll(async () => { mongoose.connect( `mongodb://${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`, diff --git a/BE/musicspot/src/journey/journey.service.ts b/BE/musicspot/src/journey/journey.service.ts index 07a23d4..7cc043c 100644 --- a/BE/musicspot/src/journey/journey.service.ts +++ b/BE/musicspot/src/journey/journey.service.ts @@ -3,6 +3,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Injectable } from '@nestjs/common'; import { StartJourneyDTO } from './dto/journeyStart.dto'; import { Journey } from './journey.schema'; + import { User } from '../user/user.schema'; import { UserService } from '../user/user.service'; import { EndJourneyDTO } from './dto/journeyEnd.dto'; @@ -14,6 +15,7 @@ export class JourneyService { @InjectModel(User.name) private userModel: Model, ) {} async insertJourneyData(startJourneyDTO: StartJourneyDTO) { + const journeyData = { ...startJourneyDTO, spots: [], diff --git a/BE/musicspot/src/spot/dto/recordSpot.dto.ts b/BE/musicspot/src/spot/dto/recordSpot.dto.ts new file mode 100644 index 0000000..2f09882 --- /dev/null +++ b/BE/musicspot/src/spot/dto/recordSpot.dto.ts @@ -0,0 +1,22 @@ +import { + ArrayMaxSize, + ArrayMinSize, + IsArray, + IsNumber, + IsString, +} from 'class-validator'; + +export class RecordSpotDTO { + @IsString() + readonly journeyId: string; + @IsArray() + @ArrayMaxSize(2, { message: 'coordinate has only 2' }) + @ArrayMinSize(2, { message: 'coordinate has only 2' }) + @IsNumber({}, { each: true }) + readonly coordinate: number[]; + + @IsString() + readonly timestamp: string; + + readonly photoData: Buffer; +} diff --git a/BE/musicspot/src/spot/spot.controller.spec.ts b/BE/musicspot/src/spot/spot.controller.spec.ts new file mode 100644 index 0000000..088e5a0 --- /dev/null +++ b/BE/musicspot/src/spot/spot.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SpotController } from './spot.controller'; + +describe('SpotController', () => { + let controller: SpotController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SpotController], + }).compile(); + + controller = module.get(SpotController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/BE/musicspot/src/spot/spot.controller.ts b/BE/musicspot/src/spot/spot.controller.ts new file mode 100644 index 0000000..d5487e3 --- /dev/null +++ b/BE/musicspot/src/spot/spot.controller.ts @@ -0,0 +1,31 @@ +import { + Controller, + Post, + Body, + UseInterceptors, + UploadedFile, +} from '@nestjs/common'; +import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { RecordSpotDTO } from './dto/recordSpot.dto'; +import { SpotService } from './spot.service'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { Spot } from './spot.schema'; +@Controller('spot') +@ApiTags('spot 관련 API') +export class SpotController { + constructor(private spotService: SpotService) {} + + @ApiOperation({ + summary: 'spot 기록 시 실행되는 API', + description: + 'request로 여정 ID(string), 위치좌표([number, number]), timestamp(string), 유저 이메일(string)을 필요합니다.', + }) + @ApiCreatedResponse({ + description: '생성된 spot 데이터를 반환', + type: Spot, + }) + @Post() + async create(@Body() recordSpotDTO: RecordSpotDTO) { + return await this.spotService.create(recordSpotDTO); + } +} diff --git a/BE/musicspot/src/spot/spot.module.ts b/BE/musicspot/src/spot/spot.module.ts new file mode 100644 index 0000000..28e69e4 --- /dev/null +++ b/BE/musicspot/src/spot/spot.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { SpotController } from './spot.controller'; +import { SpotService } from './spot.service'; +import { MongooseModule } from '@nestjs/mongoose'; +import { Spot, SpotSchema } from './spot.schema'; +import { Journey, JourneySchema } from 'src/journey/journey.schema'; +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: Spot.name, schema: SpotSchema }, + { name: Journey.name, schema: JourneySchema }, + ]), + ], + controllers: [SpotController], + providers: [SpotService], +}) +export class SpotModule {} diff --git a/BE/musicspot/src/spot/spot.schema.ts b/BE/musicspot/src/spot/spot.schema.ts new file mode 100644 index 0000000..a5ee952 --- /dev/null +++ b/BE/musicspot/src/spot/spot.schema.ts @@ -0,0 +1,21 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { HydratedDocument } from 'mongoose'; + +export type SpotDocument = HydratedDocument; + +@Schema({ collection: 'spot' }) +export class Spot { + @Prop() + journeyId: string; + + @Prop({ type: [Number] }) + coordinate: number[]; + + @Prop({ type: String }) + timestamp: string; + + @Prop({ type: String }) + photoUrl: string; +} + +export const SpotSchema = SchemaFactory.createForClass(Spot); diff --git a/BE/musicspot/src/spot/spot.service.spec.ts b/BE/musicspot/src/spot/spot.service.spec.ts new file mode 100644 index 0000000..3851cf1 --- /dev/null +++ b/BE/musicspot/src/spot/spot.service.spec.ts @@ -0,0 +1,54 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Test, TestingModule } from '@nestjs/testing'; +import { SpotService } from './spot.service'; +import mongoose from 'mongoose'; +import { Spot, SpotSchema } from './spot.schema'; +import { getModelToken } from '@nestjs/mongoose'; + +import { Journey, JourneySchema } from '../journey/journey.schema'; +import { ConfigBase } from 'aws-sdk/lib/config'; +describe('SpotService', () => { + let service: SpotService; + let spotModel; + let journeyModel; + beforeAll(async () => { + mongoose.connect('mongodb://192.168.174.128:27017/musicspotDB'); + spotModel = mongoose.model(Spot.name, SpotSchema); + journeyModel = mongoose.model(Journey.name, JourneySchema); + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SpotService, + { + provide: getModelToken(Spot.name), + useValue: spotModel, + }, + { provide: getModelToken(Journey.name), useValue: journeyModel }, + ], + }).compile(); + + service = module.get(SpotService); + }); + + it('spot 삽입 테스트', async () => { + const imagePath = path.join(__dirname, 'test/test.png'); + + // 이미지 파일 동기적으로 읽기 + const imageBuffer = fs.readFileSync(imagePath); + + const journeyId = '655b6d6bfd9f60fc689789a6'; + const data = { + journeyId, + coordinate: [10, 10], + timestamp: '1시50분', + photo: imageBuffer, + }; + const photoUrl = await service.uploadPhotoToStorage(data.photo); + const createdSpotData = await service.insertToSpot({ ...data, photoUrl }); + expect(createdSpotData.journeyId).toEqual(journeyId); + }); + + afterAll(() => { + mongoose.connection.close(); + }); +}); diff --git a/BE/musicspot/src/spot/spot.service.ts b/BE/musicspot/src/spot/spot.service.ts new file mode 100644 index 0000000..2b99558 --- /dev/null +++ b/BE/musicspot/src/spot/spot.service.ts @@ -0,0 +1,52 @@ +import { Model } from 'mongoose'; +import { Injectable } from '@nestjs/common'; + +import { InjectModel } from '@nestjs/mongoose'; +import { Spot } from './spot.schema'; +import { RecordSpotDTO } from './dto/recordSpot.dto'; +import { Journey } from '../journey/journey.schema'; +import * as AWS from 'aws-sdk'; + +const endpoint = process.env.NCLOUD_ENDPOINT; +const region = process.env.NCLOUD_REGION; +const accessKey = process.env.NCLOUD_ACCESS_KEY; +const secretKey = process.env.NCLOUD_SECRET_KEY; +const bucketName = process.env.BUCKET_NAME; + +@Injectable() +export class SpotService { + constructor( + @InjectModel(Spot.name) private spotModel: Model, + @InjectModel(Journey.name) private journeyModel: Model, + ) {} + async uploadPhotoToStorage(photoData: Buffer): Promise { + const S3 = new AWS.S3({ + endpoint, + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey: secretKey, + }, + }); + const key = `${Date.now()}`; + const result = await S3.putObject({ + Bucket: bucketName, + Key: key, + Body: photoData, + }).promise(); + return `${endpoint}/${bucketName}/${key}`; + } + async insertToSpot(spotData) { + const createdSpotData = await new this.spotModel(spotData).save(); + const spotId = createdSpotData._id; + await this.journeyModel.updateOne( + { _id: spotData.journeyId }, + { $push: { spots: spotId } }, + ); + return createdSpotData; + } + async create(recordSpotDto: RecordSpotDTO) { + const photoUrl = await this.uploadPhotoToStorage(recordSpotDto.photoData); + return await this.insertToSpot({ ...recordSpotDto, photoUrl }); + } +} diff --git a/BE/musicspot/src/spot/test/test.png b/BE/musicspot/src/spot/test/test.png new file mode 100644 index 0000000..faf1b77 Binary files /dev/null and b/BE/musicspot/src/spot/test/test.png differ