From 8e5e9bcd0b15f176065217102214a3428fef00ab Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Tue, 12 Jan 2021 23:15:53 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Fix:=20git=20=EA=BC=AC=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- README.md | 62 +++++++++++++++++++++ env.example | 10 ++++ src/place/kakaoMapSearch/search.service.ts | 46 ++++++++++++++++ src/place/place.model.ts | 64 ++++++++++++++++++++++ src/place/place.module.ts | 18 ++++++ src/place/place.resolver.ts | 30 ++++++++++ 7 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 env.example create mode 100644 src/place/kakaoMapSearch/search.service.ts create mode 100644 src/place/place.model.ts create mode 100644 src/place/place.resolver.ts diff --git a/.gitignore b/.gitignore index c16ef02..bbe605f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +.env.* \ No newline at end of file diff --git a/README.md b/README.md index bc09f75..c04921f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ - [2. 의존성 세팅](#2-의존성-세팅) - [3. 프로젝트 세팅](#3-프로젝트-세팅) - [4. 프로젝트 구조](#4-프로젝트-구조) + - [5. Playground](#5-playground) + - [6. infrastructure](#6-infrastructure) + - [7. API](#7-api) + - [8. LINKS](#8-links) @@ -109,3 +113,61 @@ cd src && mkdir shared - 기본적으로 `place/`데이터는 캐시는 되어도, db에 저장되지 않기 때문에, 상태를 가지게 되면 spot이라는 entity를 활용해 db에 저장시킵니다. - `user` - 사용자 도메인 +- `config` + - env 환경값 관리 모듈(database, 3rd-party api) + +## 5. Playground + +![](./images/search_playground.png) + +```bash +$ npm run start:dev +# open http://[::1]:8000/graphql +``` + +- 지역검색 query 예시 + - **sort를 distance로 하게되면 x,y는 필수로 넣어주어야 합니다.** + +``` +{ + placesByKeyworld(filters: { + query: "돈가스" + sort: distance + x:126.40716457908 + y:33.2588962209144 + }){ + id + place_name + x + y + } +} +``` + +## 6. infrastructure + +- 로컬 mongodb 세팅 + +```bash +docker run --name mongo -p 27017:27017 -d mongo + +``` + +## 7. API + +- 카카오 + - [지역 REST api](https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword) + - [지도 jdk](https://apis.map.kakao.com/web/guide/) + - 지역 검색 가능 + - 라이브러리를 사용하면, 마크, 클러스터링 등 다양한 서비스도 사용가능 +- 네이버(depreacted): 20.07 기준 검색 max 5개로 실사용 불가능 + - [네이버 검색(지역)](https://developers.naver.com/docs/search/local/): 식당, 정보를 검색하면 매칭되는 place object를 넘겨준다. + - [네이버 지도](https://www.ncloud.com/product/applicationService/maps): 지도를 그려준다. + - [좌표계 변환 이슈](https://github.com/navermaps/maps.js/issues/285) + - [길찾기 api](https://apidocs.ncloud.com/ko/ai-naver/maps_directions/) + - 주의사항으로 jdk 제공 되지 않는 듯하다. + +## 8. LINKS + +- [이슈: 네이버 지도에 네이버 검색 결과를 같이 띄울 수 없을까?](https://github.com/navermaps/maps.js/issues/193) +- [configService 의존성 주입](https://dev.to/kop7/how-to-build-autocomplete-search-with-nestjs-elasticsearch-and-vue-12h8) diff --git a/env.example b/env.example new file mode 100644 index 0000000..3c02005 --- /dev/null +++ b/env.example @@ -0,0 +1,10 @@ +#App +NODE_ENV=dev +NODE_PORT=8000 + +KAKAO_DEV_HOST=https://dapi.kakao.com/v2/local/search/keyword.json +KAKAO_DEV_REST_API_KEY= +KAKAO_DEV_JDK= + +KAKAO_API_MAP_HOST=https://map.kakao.com +KAKAO_API_SEARCH=/link/search \ No newline at end of file diff --git a/src/place/kakaoMapSearch/search.service.ts b/src/place/kakaoMapSearch/search.service.ts new file mode 100644 index 0000000..8ed4375 --- /dev/null +++ b/src/place/kakaoMapSearch/search.service.ts @@ -0,0 +1,46 @@ +import { Model, Types } from "mongoose"; +import Axios, { AxiosResponse } from "axios"; +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; + +import { ConfigService } from "../../config/config.service"; +import { Place, PlaceDocument } from "../place.model"; +import { KeywordSearchDto } from "./search.dto"; + +@Injectable() +export class SearchService { + constructor( + private readonly configService: ConfigService, + @InjectModel(Place.name) private placeModel: Model + ) {} + + // https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword + async searchByKeyworld( + keywordSearchDto: KeywordSearchDto + ): Promise> { + const baseUrl = this.configService.get("KAKAO_DEV_HOST"); + const { documents } = await Axios.get(baseUrl, { + headers: { + Authorization: `KakaoAK ${this.configService.get( + "KAKAO_DEV_REST_API_KEY" + )}`, + }, + params: { + ...keywordSearchDto, + }, + }) + .then((response) => response.data) + .catch((err) => console.error(err)); + + return documents; + } + + async cachePlaces( + place: AxiosResponse, + dueHour: Number + ): Promise> { + if (mongoRes && mongoRes.expirationDate.getTime() > new Date().getTime()) { + return places; + } + } +} diff --git a/src/place/place.model.ts b/src/place/place.model.ts new file mode 100644 index 0000000..252cde2 --- /dev/null +++ b/src/place/place.model.ts @@ -0,0 +1,64 @@ +import { Field, Float, ObjectType } from "@nestjs/graphql"; +import * as mongoose from "mongoose"; +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; + +// https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword +@ObjectType() +@Schema() +export class Place { + // @Prop({ required: true }) + @Field(() => String) + @Prop({ required: true, unique: true }) + id: string; + + @Field(() => String) + @Prop({ required: true }) + place_name: string; + + @Field(() => String, { nullable: true }) + @Prop() + category_name?: string; + + @Field(() => String, { nullable: true }) + @Prop() + category_group_code?: string; + + @Field(() => String, { nullable: true }) + @Prop() + category_group_name?: number; + + @Field(() => String, { nullable: true }) + @Prop() + phone?: string; + + @Field(() => String, { nullable: true }) + @Prop() + address_name?: string; + + @Field(() => String, { nullable: true }) + @Prop() + road_address_name?: string; + + @Field(() => String, { nullable: true }) + @Prop() + place_url?: string; + + @Field(() => String, { nullable: true }) + @Prop() + distance?: string; + + @Field((type) => Float, { nullable: true }) + @Prop() + x?: number; + + @Field((type) => Float, { nullable: true }) + @Prop() + y?: number; + + @Prop({ required: true, type: Date }) + expirationDate: Date; +} + +// for cache +export type PlaceDocument = Place & mongoose.Document; +export const PlaceSchema = SchemaFactory.createForClass(Place); diff --git a/src/place/place.module.ts b/src/place/place.module.ts index 5d2223b..4e66e1a 100644 --- a/src/place/place.module.ts +++ b/src/place/place.module.ts @@ -1,4 +1,22 @@ +<<<<<<< Updated upstream import { Module } from '@nestjs/common'; @Module({}) +======= +import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; +import { Place, PlaceSchema } from "./place.model"; + +import { ConfigModule } from "../config/config.module"; +import { SearchService } from "./kakaoMapSearch/search.service"; +import { PlaceResolver } from "./place.resolver"; + +@Module({ + imports: [ + ConfigModule, + MongooseModule.forFeature([{ name: Place.name, schema: PlaceSchema }]), + ], + providers: [SearchService, PlaceResolver], +}) +>>>>>>> Stashed changes export class PlaceModule {} diff --git a/src/place/place.resolver.ts b/src/place/place.resolver.ts new file mode 100644 index 0000000..d8b61a1 --- /dev/null +++ b/src/place/place.resolver.ts @@ -0,0 +1,30 @@ +import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; + +import { Place } from "./place.model"; +import { SearchService } from "./kakaoMapSearch/search.service"; +import { KeywordSearchDto } from "./kakaoMapSearch/search.dto"; + +@Resolver(() => Place) +export class PlaceResolver { + constructor(private readonly searchService: SearchService) {} + + @Query(() => [Place]) + async placesByKeyworld( + @Args("filters") filters: KeywordSearchDto + ): Promise { + const searchedPlaces = await this.searchService.searchByKeyworld(filters); + + searchedPlaces.data.forEach((place:Place) => { + + if () + + }) + + const cachedPlaces = await this.searchService.cachePlaces( + searchedPlaces, + 12 + ); + + return cachedPlaces; + } +} From c23883a770addba187f2110a4db5ab5519559294 Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Thu, 14 Jan 2021 22:54:55 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Add:=20Spot=20=EB=AA=A8=EB=93=88=20/=20Enti?= =?UTF-8?q?ty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 72 +++++++++++++++++++ package.json | 2 + src/app.module.ts | 5 +- src/place/place.entity.ts | 41 +++++++++++ .../entities/spot.entity.ts} | 20 ++---- src/spot/spot.module.ts | 16 ++++- 6 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 src/place/place.entity.ts rename src/{place/place.model.ts => spot/entities/spot.entity.ts} (69%) diff --git a/package-lock.json b/package-lock.json index eabe2b4..455efc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4102,6 +4102,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -4555,6 +4560,63 @@ "unset-value": "^1.0.0" } }, + "cache-manager": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-3.4.0.tgz", + "integrity": "sha512-+WtL5sKHGngtnzTHNFA6+gC0wjpAAUmwmprXOSeaCBOkohM8Nh7GvV8fC90NFrDh7m3i87AshGd39/yYbWNtWA==", + "requires": { + "async": "^3.2.0", + "lodash": "^4.17.20", + "lru-cache": "6.0.0" + } + }, + "cache-manager-mongodb": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/cache-manager-mongodb/-/cache-manager-mongodb-0.3.0.tgz", + "integrity": "sha512-r+piWvu8XD8ceBWflZBEQZKemksoaL1V5zSHWyviVTXiGBSVQAf18b4S1v5UwGZMJnrMk8YNFgpBlugF0nuhvg==", + "requires": { + "bluebird": "^3.5.3", + "cache-manager": "^2.9.0", + "lodash": "^4.17.15", + "mongodb": "^3.6.3" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "cache-manager": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-2.11.1.tgz", + "integrity": "sha512-XhUuc9eYwkzpK89iNewFwtvcDYMUsvtwzHeyEOPJna/WsVsXcrzsA1ft2M0QqPNunEzLhNCYPo05tEfG+YuNow==", + "requires": { + "async": "1.5.2", + "lodash.clonedeep": "4.5.0", + "lru-cache": "4.0.0" + } + }, + "lru-cache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.0.tgz", + "integrity": "sha1-tcvwFVbBaWb+vlTO7A+03JDfbCg=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, "call-bind": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.1.tgz", @@ -7998,6 +8060,11 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "optional": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9276,6 +9343,11 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", diff --git a/package.json b/package.json index e3ef488..82547f9 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "@nestjs/mongoose": "^7.2.1", "@nestjs/platform-express": "^7.5.1", "apollo-server-express": "^2.19.1", + "cache-manager": "^3.4.0", + "cache-manager-mongodb": "^0.3.0", "class-transformer": "^0.3.1", "class-validator": "^0.12.2", "dotenv": "^8.2.0", diff --git a/src/app.module.ts b/src/app.module.ts index 92d8946..062c57e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,8 +1,8 @@ import { Module } from "@nestjs/common"; -import { ConfigModule } from "./config/config.module"; import { join } from "path"; - import { GraphQLModule } from "@nestjs/graphql"; + +import { ConfigModule } from "./config/config.module"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { EmojiModule } from "./emoji/emoji.module"; @@ -19,6 +19,7 @@ import { UserModule } from "./user/user.module"; SpotModule, PlaceModule, UserModule, + SpotModule, GraphQLModule.forRoot({ autoSchemaFile: join(process.cwd(), "src/schema.gql"), debug: false, diff --git a/src/place/place.entity.ts b/src/place/place.entity.ts new file mode 100644 index 0000000..3bdfad4 --- /dev/null +++ b/src/place/place.entity.ts @@ -0,0 +1,41 @@ +import { Field, ObjectType, Float } from "@nestjs/graphql"; + +// https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword +@ObjectType() +export class Place { + @Field(() => String) + id: string; + + @Field(() => String) + place_name: string; + + @Field(() => String, { nullable: true }) + category_name?: string; + + @Field(() => String, { nullable: true }) + category_group_code?: string; + + @Field(() => String, { nullable: true }) + category_group_name?: number; + + @Field(() => String, { nullable: true }) + phone?: string; + + @Field(() => String, { nullable: true }) + address_name?: string; + + @Field(() => String, { nullable: true }) + road_address_name?: string; + + @Field(() => String, { nullable: true }) + place_url?: string; + + @Field(() => String, { nullable: true }) + distance?: string; + + @Field((type) => Float, { nullable: true }) + x?: number; + + @Field((type) => Float, { nullable: true }) + y?: number; +} diff --git a/src/place/place.model.ts b/src/spot/entities/spot.entity.ts similarity index 69% rename from src/place/place.model.ts rename to src/spot/entities/spot.entity.ts index 252cde2..1249f93 100644 --- a/src/place/place.model.ts +++ b/src/spot/entities/spot.entity.ts @@ -1,15 +1,13 @@ -import { Field, Float, ObjectType } from "@nestjs/graphql"; +import { ObjectType, Field, Int, Float } from "@nestjs/graphql"; import * as mongoose from "mongoose"; import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; -// https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword @ObjectType() -@Schema() -export class Place { - // @Prop({ required: true }) - @Field(() => String) +@Schema({ timestamps: true }) // graphql 은 timestamp 삽입 어떻게 할까? +export class Spot { @Prop({ required: true, unique: true }) - id: string; + @Field(() => String, { description: "카카오 Place id" }) + _id: string; @Field(() => String) @Prop({ required: true }) @@ -54,11 +52,7 @@ export class Place { @Field((type) => Float, { nullable: true }) @Prop() y?: number; - - @Prop({ required: true, type: Date }) - expirationDate: Date; } -// for cache -export type PlaceDocument = Place & mongoose.Document; -export const PlaceSchema = SchemaFactory.createForClass(Place); +export type SpotDocument = Spot & mongoose.Document; +export const SpotSchema = SchemaFactory.createForClass(Spot); diff --git a/src/spot/spot.module.ts b/src/spot/spot.module.ts index 2bdc251..b58c300 100644 --- a/src/spot/spot.module.ts +++ b/src/spot/spot.module.ts @@ -1,4 +1,16 @@ -import { Module } from '@nestjs/common'; +import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; -@Module({}) +import { Spot, SpotSchema } from "./entities/spot.entity"; +import { SpotService } from "./spot.service"; +import { SpotResolver } from "./spot.resolver"; +import { ConfigModule } from "src/config/config.module"; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: Spot.name, schema: SpotSchema }]), + ConfigModule, + ], + providers: [SpotResolver, SpotService], +}) export class SpotModule {} From be2b76b1621411f1006d554397c73edee107ac07 Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Thu, 14 Jan 2021 23:10:29 +0900 Subject: [PATCH 3/8] Add: Spot dto --- src/spot/dto/create-spot.input.ts | 40 +++++++++++++++++++++++++++++++ src/spot/dto/update-spot.input.ts | 14 +++++++++++ src/spot/entities/spot.entity.ts | 7 +++++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/spot/dto/create-spot.input.ts create mode 100644 src/spot/dto/update-spot.input.ts diff --git a/src/spot/dto/create-spot.input.ts b/src/spot/dto/create-spot.input.ts new file mode 100644 index 0000000..0f07482 --- /dev/null +++ b/src/spot/dto/create-spot.input.ts @@ -0,0 +1,40 @@ +import { InputType, Int, Float, Field } from "@nestjs/graphql"; + +@InputType() +export class CreateSpotInput { + @Field(() => String, { description: "카카오 Place id" }) + _id: string; + + @Field(() => String) + place_name: string; + + @Field((type) => Float) + x: number; + + @Field((type) => Float) + y: number; + + @Field(() => String, { nullable: true }) + category_name?: string; + + @Field(() => String, { nullable: true }) + category_group_code?: string; + + @Field(() => String, { nullable: true }) + category_group_name?: number; + + @Field(() => String, { nullable: true }) + phone?: string; + + @Field(() => String, { nullable: true }) + address_name?: string; + + @Field(() => String, { nullable: true }) + road_address_name?: string; + + @Field(() => String, { nullable: true }) + place_url?: string; + + @Field(() => String, { nullable: true }) + distance?: string; +} diff --git a/src/spot/dto/update-spot.input.ts b/src/spot/dto/update-spot.input.ts new file mode 100644 index 0000000..202f100 --- /dev/null +++ b/src/spot/dto/update-spot.input.ts @@ -0,0 +1,14 @@ +import { CreateSpotInput } from "./create-spot.input"; +import { InputType, Field, Int, Float, PartialType } from "@nestjs/graphql"; + +@InputType() +export class UpdateSpotInput extends PartialType(CreateSpotInput) { + @Field(() => String, { nullable: true }) + place_name?: string; + + @Field((type) => Float, { nullable: true }) + x?: number; + + @Field((type) => Float, { nullable: true }) + y?: number; +} diff --git a/src/spot/entities/spot.entity.ts b/src/spot/entities/spot.entity.ts index 1249f93..55831d5 100644 --- a/src/spot/entities/spot.entity.ts +++ b/src/spot/entities/spot.entity.ts @@ -9,8 +9,8 @@ export class Spot { @Field(() => String, { description: "카카오 Place id" }) _id: string; - @Field(() => String) @Prop({ required: true }) + @Field(() => String) place_name: string; @Field(() => String, { nullable: true }) @@ -52,6 +52,11 @@ export class Spot { @Field((type) => Float, { nullable: true }) @Prop() y?: number; + + // https://github.com/LotfiMEZIANI/Three-in-one-blog-post/blob/8cc58d094bad5c1a3ca0514ec1d6a6282724313b/src/app/person/person.model.ts#L19 + // @Field(() => [Emoji]) + // @Prop({ type: [mongoose.Types.ObjectId], ref: Emoji.name }) + // emojis: mongoose.Types.ObjectId[] | Emoji[]; } export type SpotDocument = Spot & mongoose.Document; From c373c31e0c551b54c96aba077b375f1199a277af Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Thu, 14 Jan 2021 23:21:26 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Add:=20Spot=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20(create=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/place/kakaoMapSearch/search.dto.ts | 1 - src/spot/spot.service.spec.ts | 18 ++++++++++++ src/spot/spot.service.ts | 38 ++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/spot/spot.service.spec.ts create mode 100644 src/spot/spot.service.ts diff --git a/src/place/kakaoMapSearch/search.dto.ts b/src/place/kakaoMapSearch/search.dto.ts index f74d678..d236b3e 100644 --- a/src/place/kakaoMapSearch/search.dto.ts +++ b/src/place/kakaoMapSearch/search.dto.ts @@ -5,7 +5,6 @@ import { Int, registerEnumType, } from "@nestjs/graphql"; -import { IsNotEmpty, IsOptional, Length, MaxLength } from "class-validator"; enum SortType { distance = "distance", diff --git a/src/spot/spot.service.spec.ts b/src/spot/spot.service.spec.ts new file mode 100644 index 0000000..2835270 --- /dev/null +++ b/src/spot/spot.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SpotService } from './spot.service'; + +describe('SpotService', () => { + let service: SpotService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SpotService], + }).compile(); + + service = module.get(SpotService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/spot/spot.service.ts b/src/spot/spot.service.ts new file mode 100644 index 0000000..af327fc --- /dev/null +++ b/src/spot/spot.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model, Types } from "mongoose"; + +import { SearchService } from "src/place/kakaoMapSearch/search.service"; +import { CreateSpotInput } from "./dto/create-spot.input"; +import { UpdateSpotInput } from "./dto/update-spot.input"; +import { Spot, SpotDocument } from "./entities/spot.entity"; + +@Injectable() +export class SpotService { + constructor( + private readonly searchService: SearchService, + @InjectModel(Spot.name) private spotModel: Model + ) {} + + async create(createSpotInput: CreateSpotInput) { + // TODO: cache find + const createdSpot = new this.spotModel(createSpotInput); + return createdSpot.save(); + } + + update(id: number, updateSpotInput: UpdateSpotInput) { + return `This action updates a #${id} spot`; + } + + findAll() { + return `This action returns all spot`; + } + + findOne(id: number) { + return `This action returns a #${id} spot`; + } + + remove(id: number) { + return `This action removes a #${id} spot`; + } +} From 6d21c34feedcbf436dea7667398a8e525ca49af5 Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Thu, 14 Jan 2021 23:22:55 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Add:=20mongoDB=20=EC=BA=90=EC=8B=B1(=20plac?= =?UTF-8?q?e,=20ttl:=20300=20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/place/kakaoMapSearch/search.service.ts | 27 ++++++++----------- src/place/place.module.ts | 17 +++++++++--- src/place/place.resolver.ts | 31 ++++++++++++---------- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/place/kakaoMapSearch/search.service.ts b/src/place/kakaoMapSearch/search.service.ts index 2a2e287..331e072 100644 --- a/src/place/kakaoMapSearch/search.service.ts +++ b/src/place/kakaoMapSearch/search.service.ts @@ -1,25 +1,23 @@ -import { Model, Types } from "mongoose"; import Axios, { AxiosResponse } from "axios"; -import { Injectable } from "@nestjs/common"; -import { InjectModel } from "@nestjs/mongoose"; +import { Injectable, Inject, CACHE_MANAGER } from "@nestjs/common"; +import { Cache } from "cache-manager"; import { ConfigService } from "../../config/config.service"; -import { Place, PlaceDocument } from "../place.model"; import { KeywordSearchDto } from "./search.dto"; @Injectable() export class SearchService { constructor( private readonly configService: ConfigService, - @InjectModel(Place.name) private placeModel: Model + @Inject(CACHE_MANAGER) private cacheManager: Cache ) {} // https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword async searchByKeyworld( keywordSearchDto: KeywordSearchDto - ): Promise> { + ): Promise> { const baseUrl = this.configService.get("KAKAO_DEV_HOST"); - const { documents } = await Axios.get(baseUrl, { + return Axios.get(baseUrl, { headers: { Authorization: `KakaoAK ${this.configService.get( "KAKAO_DEV_REST_API_KEY" @@ -29,18 +27,15 @@ export class SearchService { ...keywordSearchDto, }, }) - .then((response) => response.data) + .then((response) => response.data.documents) .catch((err) => console.error(err)); + } - return documents; + async setPlaceFromCacheById(key, value) { + return this.cacheManager.set(key, value); } - async cachePlaces( - place: AxiosResponse, - dueHour: Number - ): { - if (mongoRes && mongoRes.expirationDate.getTime() > new Date().getTime()) { - return places; - } + async getPlaceFromCacheById(key) { + return this.cacheManager.get(key); } } diff --git a/src/place/place.module.ts b/src/place/place.module.ts index eda4286..1c4a093 100644 --- a/src/place/place.module.ts +++ b/src/place/place.module.ts @@ -1,7 +1,7 @@ -import { Module } from "@nestjs/common"; -import { MongooseModule } from "@nestjs/mongoose"; +import { Module, CacheModule } from "@nestjs/common"; +import * as cacheManager from "cache-manager"; +import * as mongoStore from "cache-manager-mongodb"; -import { Place, PlaceSchema } from "./place.model"; import { ConfigModule } from "../config/config.module"; import { SearchService } from "./kakaoMapSearch/search.service"; import { PlaceResolver } from "./place.resolver"; @@ -9,7 +9,16 @@ import { PlaceResolver } from "./place.resolver"; @Module({ imports: [ ConfigModule, - MongooseModule.forFeature([{ name: Place.name, schema: PlaceSchema }]), + CacheModule.register({ + store: mongoStore, + uri: "mongodb://0.0.0.0:27017/nodeCacheDb", + options: { + collection: "cacheManager", + compression: false, + poolSize: 5, + }, + ttl: 300, + }), ], providers: [SearchService, PlaceResolver], }) diff --git a/src/place/place.resolver.ts b/src/place/place.resolver.ts index d8b61a1..f2cded3 100644 --- a/src/place/place.resolver.ts +++ b/src/place/place.resolver.ts @@ -1,6 +1,6 @@ -import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; +import { Args, Query, Resolver } from "@nestjs/graphql"; -import { Place } from "./place.model"; +import { Place } from "./place.entity"; import { SearchService } from "./kakaoMapSearch/search.service"; import { KeywordSearchDto } from "./kakaoMapSearch/search.dto"; @@ -12,19 +12,22 @@ export class PlaceResolver { async placesByKeyworld( @Args("filters") filters: KeywordSearchDto ): Promise { - const searchedPlaces = await this.searchService.searchByKeyworld(filters); + const places: any = await this.searchService.searchByKeyworld(filters); - searchedPlaces.data.forEach((place:Place) => { - - if () - - }) - - const cachedPlaces = await this.searchService.cachePlaces( - searchedPlaces, - 12 - ); + for await (let p of places) { + const isCached = await this.searchService.getPlaceFromCacheById(p.id); + console.log(isCached); + if (isCached) continue; + this.searchService.setPlaceFromCacheById(p.id, p); + } + return places; + } - return cachedPlaces; + // get place from cache (for test) + @Query(() => Place) + async getPlace( + @Args("placeId", { type: () => String }) placeId: string + ): Promise { + return await this.searchService.getPlaceFromCacheById(placeId); } } From 88b37041cfd106224950eed6442fb093dc339605 Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Fri, 15 Jan 2021 00:13:28 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Fix:=20mongoose=20=205.11.x=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EB=A7=90?= =?UTF-8?q?=EA=B2=83!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - https://github.com/Automattic/mongoose/issues/9606 --- package-lock.json | 77 ++++++++++++++++++++--------------------------- package.json | 5 +-- 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 455efc4..5d53b88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,40 +44,38 @@ } }, "@apollo/client": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.6.tgz", - "integrity": "sha512-XSm/STyNS8aHdDigLLACKNMHwI0qaQmEHWHtTP+jHe/E1wZRnn66VZMMgwKLy2V4uHISHfmiZ4KpUKDPeJAKqg==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.2.9.tgz", + "integrity": "sha512-AUvYITKhJNfRNU/Cf8t/N628ADdVah1+l9Qtjd09IwScRfDGvbBTkHMAgcb6hl7vuBVqGwQRq6fPKzHgWRlisg==", "requires": { "@graphql-typed-document-node/core": "^3.0.0", "@types/zen-observable": "^0.8.0", "@wry/context": "^0.5.2", - "@wry/equality": "^0.3.0", + "@wry/equality": "^0.2.0", "fast-json-stable-stringify": "^2.0.0", "graphql-tag": "^2.11.0", "hoist-non-react-statics": "^3.3.2", - "optimism": "^0.13.1", + "optimism": "^0.13.0", "prop-types": "^15.7.2", "symbol-observable": "^2.0.0", - "ts-invariant": "^0.6.0", + "ts-invariant": "^0.5.0", "tslib": "^1.10.0", "zen-observable": "^0.8.14" }, "dependencies": { "@wry/equality": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.3.1.tgz", - "integrity": "sha512-8/Ftr3jUZ4EXhACfSwPIfNsE8V6WKesdjp+Dxi78Bej6qlasAxiz0/F8j0miACRj9CL4vC5Y5FsfwwEYAuhWbg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz", + "integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==", "requires": { - "tslib": "^1.14.1" + "tslib": "^1.9.3" } }, "ts-invariant": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.6.0.tgz", - "integrity": "sha512-caoafsfgb8QxdrKzFfjKt627m4i8KTtfAiji0DYJfWI4A/S9ORNNpzYuD9br64kyKFgxn9UNaLLbSupam84mCA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.5.1.tgz", + "integrity": "sha512-k3UpDNrBZpqJFnAAkAHNmSHtNuCxcU6xLiziPgalHRKZHme6T6jnKC8CcXDmk1zbHLQM8pc+rNC1Q6FvXMAl+g==", "requires": { - "@types/ungap__global-this": "^0.3.1", - "@ungap/global-this": "^0.4.2", "tslib": "^1.9.3" } }, @@ -2101,9 +2099,9 @@ "integrity": "sha512-FROYmmZ2F+tLJP/aHasPMX40iUHQPtEAzOAcfAp21baebN5iLUrdyTuphoXjIqubfPFSwtnAGpVm9kLJjQ//ig==" }, "@nestjs/mongoose": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-7.2.1.tgz", - "integrity": "sha512-KFpWwmnl+Uv5c1TceymAsclI8afXaT7TJfTFUCj/vzhbB97ZSX3eQb4mIeQl6WdqAGGNuOUFCVy8/Phk2w541A==" + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-7.0.2.tgz", + "integrity": "sha512-EXTLgZ3OuzE7ZDH628+otNxzKTaoQmgEC/KeUc/k1Na96BfTT1IUqMWaWEnzkI1ALLJQOsjKKDc0RgJ0nebZPg==" }, "@nestjs/platform-express": { "version": "7.6.5", @@ -2393,6 +2391,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dev": true, "requires": { "@types/node": "*" } @@ -2611,6 +2610,7 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", "integrity": "sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q==", + "dev": true, "requires": { "@types/bson": "*", "@types/node": "*" @@ -2731,11 +2731,6 @@ } } }, - "@types/ungap__global-this": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@types/ungap__global-this/-/ungap__global-this-0.3.1.tgz", - "integrity": "sha512-+/DsiV4CxXl6ZWefwHZDXSe1Slitz21tom38qPCaG0DYCS1NnDPIQDTKcmQ/tvK/edJUKkmuIDBJbmKDiB0r/g==" - }, "@types/validator": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.0.0.tgz", @@ -2942,11 +2937,6 @@ "eslint-visitor-keys": "^2.0.0" } }, - "@ungap/global-this": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@ungap/global-this/-/global-this-0.4.3.tgz", - "integrity": "sha512-MuHEpDBurNVeD6mV9xBcAN2wfTwuaFQhHuhWkJuXmyVJ5P5sBCw+nnFpdfb0tAvgWkfefWCsAoAsh7MTUr3LPg==" - }, "@vue/compiler-core": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.5.tgz", @@ -7975,9 +7965,9 @@ } }, "kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", + "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" }, "kind-of": { "version": "6.0.3", @@ -8471,17 +8461,16 @@ } }, "mongoose": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.11.tgz", - "integrity": "sha512-JgKKAosJf6medPOZi2LmO7sMz7Sg00mgjyPAKari3alzL+R/n8D+zKK29iGtJpNNtv9IKy14H37CWuiaZ7016w==", + "version": "5.10.19", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.19.tgz", + "integrity": "sha512-SuJwbhQpfZ6WZFM6H2v0Hv1wQNLDeBDLZwOHR3UnR6IlPLKjuLyEx4OLI0vFQihv+JWJKWqJt+LcRRbRyL9PCg==", "requires": { - "@types/mongodb": "^3.5.27", "bson": "^1.1.4", - "kareem": "2.3.2", + "kareem": "2.3.1", "mongodb": "3.6.3", "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.3", - "mquery": "3.2.3", + "mpath": "0.7.0", + "mquery": "3.2.2", "ms": "2.1.2", "regexp-clone": "1.0.0", "safe-buffer": "5.2.1", @@ -8507,14 +8496,14 @@ "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" }, "mpath": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", - "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", + "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" }, "mquery": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", - "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", + "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", "requires": { "bluebird": "3.5.1", "debug": "3.1.0", diff --git a/package.json b/package.json index 82547f9..09cd1be 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@nestjs/core": "^7.5.1", "@nestjs/graphql": "^7.9.4", "@nestjs/mapped-types": "^0.1.1", - "@nestjs/mongoose": "^7.2.1", + "@nestjs/mongoose": "^7.0.2", "@nestjs/platform-express": "^7.5.1", "apollo-server-express": "^2.19.1", "cache-manager": "^3.4.0", @@ -35,12 +35,13 @@ "dotenv": "^8.2.0", "graphql": "^15.4.0", "graphql-tools": "^7.0.2", - "mongoose": "^5.11.11", + "mongoose": "^5.10.19", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.6.3" }, "devDependencies": { + "@apollo/client": "^3.2.9", "@nestjs/cli": "^7.5.1", "@nestjs/schematics": "^7.1.3", "@nestjs/testing": "^7.5.1", From 144e03b2bb94f75826840d6da8b418241e20e227 Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Fri, 15 Jan 2021 00:16:29 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Add:=20=EC=8A=A4=ED=8C=9F=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=AA=BD=EA=B3=A0=20db=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 2 ++ src/place/place.module.ts | 25 +++++++++-------- src/schema.gql | 56 +++++++++++++++++++++++++++++++++++++++ src/spot/spot.module.ts | 1 + 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 062c57e..62c0f11 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from "@nestjs/common"; import { join } from "path"; import { GraphQLModule } from "@nestjs/graphql"; +import { MongooseModule } from "@nestjs/mongoose"; import { ConfigModule } from "./config/config.module"; import { AppController } from "./app.controller"; @@ -25,6 +26,7 @@ import { UserModule } from "./user/user.module"; debug: false, playground: true, }), + MongooseModule.forRoot("mongodb://localhost:27017/mydb"), ], controllers: [AppController], providers: [AppService], diff --git a/src/place/place.module.ts b/src/place/place.module.ts index 1c4a093..6ced159 100644 --- a/src/place/place.module.ts +++ b/src/place/place.module.ts @@ -6,20 +6,19 @@ import { ConfigModule } from "../config/config.module"; import { SearchService } from "./kakaoMapSearch/search.service"; import { PlaceResolver } from "./place.resolver"; +const cacheConfig = { + store: mongoStore, + uri: "mongodb://0.0.0.0:27017/nodeCacheDb", + options: { + collection: "cacheManager", + compression: false, + poolSize: 5, + }, + ttl: 300, +}; + @Module({ - imports: [ - ConfigModule, - CacheModule.register({ - store: mongoStore, - uri: "mongodb://0.0.0.0:27017/nodeCacheDb", - options: { - collection: "cacheManager", - compression: false, - poolSize: 5, - }, - ttl: 300, - }), - ], + imports: [ConfigModule, CacheModule.register(cacheConfig)], providers: [SearchService, PlaceResolver], }) export class PlaceModule {} diff --git a/src/schema.gql b/src/schema.gql index b841490..808cac1 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -2,6 +2,22 @@ # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) # ------------------------------------------------------ +type Spot { + """카카오 Place id""" + _id: String! + place_name: String! + category_name: String + category_group_code: String + category_group_name: String + phone: String + address_name: String + road_address_name: String + place_url: String + distance: String + x: Float + y: Float +} + type Place { id: String! place_name: String! @@ -18,7 +34,9 @@ type Place { } type Query { + spot(id: Int!): Spot! placesByKeyworld(filters: KeywordSearchDto!): [Place!]! + getPlace(placeId: String!): Place! } input KeywordSearchDto { @@ -37,3 +55,41 @@ enum SortType { distance accuracy } + +type Mutation { + createSpot(createSpotInput: CreateSpotInput!): Spot! + updateSpot(updateSpotInput: UpdateSpotInput!): Spot! + removeSpot(id: Int!): Spot! +} + +input CreateSpotInput { + """카카오 Place id""" + _id: String! + place_name: String! + x: Float! + y: Float! + category_name: String + category_group_code: String + category_group_name: String + phone: String + address_name: String + road_address_name: String + place_url: String + distance: String +} + +input UpdateSpotInput { + """카카오 Place id""" + _id: String + place_name: String + x: Float + y: Float + category_name: String + category_group_code: String + category_group_name: String + phone: String + address_name: String + road_address_name: String + place_url: String + distance: String +} diff --git a/src/spot/spot.module.ts b/src/spot/spot.module.ts index b58c300..991ab8a 100644 --- a/src/spot/spot.module.ts +++ b/src/spot/spot.module.ts @@ -4,6 +4,7 @@ import { MongooseModule } from "@nestjs/mongoose"; import { Spot, SpotSchema } from "./entities/spot.entity"; import { SpotService } from "./spot.service"; import { SpotResolver } from "./spot.resolver"; + import { ConfigModule } from "src/config/config.module"; @Module({ From 652e761084f94d7ad51341915778df898a4d0fbe Mon Sep 17 00:00:00 2001 From: minkj1992 Date: Fri, 15 Jan 2021 00:17:15 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Add:=20=EC=8A=A4=ED=8C=9F=20GraphQL=20resol?= =?UTF-8?q?ver=20=EC=B6=94=EA=B0=80=20(create=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++++++++++++++++++++++------ src/spot/spot.resolver.spec.ts | 19 ++++++++++++++++++ src/spot/spot.resolver.ts | 35 ++++++++++++++++++++++++++++++++++ src/spot/spot.service.ts | 16 ++++++---------- 4 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 src/spot/spot.resolver.spec.ts create mode 100644 src/spot/spot.resolver.ts diff --git a/README.md b/README.md index db7c75b..ef16669 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ - [6. infrastructure](#6-infrastructure) - [7. API](#7-api) - [8. LINKS](#8-links) + - [9. SCHEMA of GraphQL API](#9-schema-of-graphql-api) @@ -77,7 +78,11 @@ npm i @nestjs/graphql graphql-tools graphql apollo-server-express # https://dev.to/kop7/how-to-build-autocomplete-search-with-nestjs-elasticsearch-and-vue-12h8 npm i dotenv - +# cache +# https://github.com/BryanDonovan/node-cache-manager#store-engines +npm install cache-manager +# https://github.com/v4l3r10/node-cache-manager-mongodb +npm install cache-manager-mongodb --save ``` ## 3. 프로젝트 세팅 @@ -135,7 +140,7 @@ $ npm run start:dev # open http://[::1]:8000/graphql ``` -- 지역검색 query 예시 [// API 스키마 전체 ⬇](#SCHEMA-of-GraphQL-API) +- 지역검색 query 예시 [// API 스키마 전체 ⬇](#SCHEMA-of-GraphQL-API) - **sort를 distance로 하게되면 x,y는 필수로 넣어주어야 합니다.** ``` @@ -152,6 +157,20 @@ $ npm run start:dev y } } + +mutation { + createSpot(createSpotInput: { + _id: "1890778114" + place_name: "연돈" + x:126.40716457908 + y:33.2588962209144 + }){ + _id + place_name + x + y + } +} ``` ## 6. infrastructure @@ -159,8 +178,7 @@ $ npm run start:dev - 로컬 mongodb 세팅 ```bash -docker run --name mongo -p 27017:27017 -d mongo - +docker run --name mongo -p 0.0.0.0:27017:27017 -d mongo ``` ## 7. API @@ -186,7 +204,7 @@ docker run --name mongo -p 27017:27017 -d mongo
-### SCHEMA of GraphQL API +## 9. SCHEMA of GraphQL API ``` directive @specifiedBy(url: String!) on SCALAR @@ -225,4 +243,4 @@ enum SortType { distance accuracy } -``` \ No newline at end of file +``` diff --git a/src/spot/spot.resolver.spec.ts b/src/spot/spot.resolver.spec.ts new file mode 100644 index 0000000..4bfa91c --- /dev/null +++ b/src/spot/spot.resolver.spec.ts @@ -0,0 +1,19 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SpotResolver } from './spot.resolver'; +import { SpotService } from './spot.service'; + +describe('SpotResolver', () => { + let resolver: SpotResolver; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SpotResolver, SpotService], + }).compile(); + + resolver = module.get(SpotResolver); + }); + + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); +}); diff --git a/src/spot/spot.resolver.ts b/src/spot/spot.resolver.ts new file mode 100644 index 0000000..a388a6c --- /dev/null +++ b/src/spot/spot.resolver.ts @@ -0,0 +1,35 @@ +import { Resolver, Query, Mutation, Args, Int } from "@nestjs/graphql"; +import { SpotService } from "./spot.service"; +import { Spot } from "./entities/spot.entity"; +import { CreateSpotInput } from "./dto/create-spot.input"; +import { UpdateSpotInput } from "./dto/update-spot.input"; + +@Resolver(() => Spot) +export class SpotResolver { + constructor(private readonly spotService: SpotService) {} + + @Mutation(() => Spot) + async createSpot(@Args("createSpotInput") createSpotInput: CreateSpotInput) { + return await this.spotService.create(createSpotInput); + } + + @Query(() => [Spot], { name: "spot" }) + async findAll() { + return this.spotService.findAll(); + } + + @Query(() => Spot, { name: "spot" }) + async findOne(@Args("id", { type: () => Int }) id: number) { + return this.spotService.findOne(id); + } + + @Mutation(() => Spot) + async updateSpot(@Args("updateSpotInput") updateSpotInput: UpdateSpotInput) { + return this.spotService.update(updateSpotInput._id, updateSpotInput); + } + + @Mutation(() => Spot) + async removeSpot(@Args("id", { type: () => Int }) id: number) { + return this.spotService.remove(id); + } +} diff --git a/src/spot/spot.service.ts b/src/spot/spot.service.ts index af327fc..d5c9f78 100644 --- a/src/spot/spot.service.ts +++ b/src/spot/spot.service.ts @@ -2,17 +2,13 @@ import { Injectable } from "@nestjs/common"; import { InjectModel } from "@nestjs/mongoose"; import { Model, Types } from "mongoose"; -import { SearchService } from "src/place/kakaoMapSearch/search.service"; import { CreateSpotInput } from "./dto/create-spot.input"; import { UpdateSpotInput } from "./dto/update-spot.input"; import { Spot, SpotDocument } from "./entities/spot.entity"; @Injectable() export class SpotService { - constructor( - private readonly searchService: SearchService, - @InjectModel(Spot.name) private spotModel: Model - ) {} + constructor(@InjectModel(Spot.name) private spotModel: Model) {} async create(createSpotInput: CreateSpotInput) { // TODO: cache find @@ -20,19 +16,19 @@ export class SpotService { return createdSpot.save(); } - update(id: number, updateSpotInput: UpdateSpotInput) { - return `This action updates a #${id} spot`; + async update(_id: string, updateSpotInput: UpdateSpotInput) { + return `This action updates a #${_id} spot`; } - findAll() { + async findAll() { return `This action returns all spot`; } - findOne(id: number) { + async findOne(id: number) { return `This action returns a #${id} spot`; } - remove(id: number) { + async remove(id: number) { return `This action removes a #${id} spot`; } }