From aea372b78bb8a9fdee5ec0395ff3f28c3540568e Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 20 May 2023 19:21:24 +0900 Subject: [PATCH 01/15] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 120 +++++++++++++++++++++++++++++++++++----------- package.json | 1 + 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9a2e169..3fb92f6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GNU", "dependencies": { "@babel/plugin-proposal-decorators": "^7.22.3", + "@opensearch-project/opensearch": "^1.2.0", "@types/swagger-ui-express": "^4.1.3", "@types/winston": "^2.4.4", "aws-sdk": "^2.1101.0", @@ -2082,6 +2083,26 @@ "node": ">= 8" } }, + "node_modules/@opensearch-project/opensearch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-1.2.0.tgz", + "integrity": "sha512-bX0aUz5e7rlY1lKz1rFrqnNbl/l1CHvvysYB2Jn+C3WNs7nL6FnQjuxLhGwyRdW9W1bFokDoOVgPMIOi/Nn9/g==", + "dependencies": { + "aws4": "^1.11.0", + "debug": "^4.3.1", + "hpagent": "^0.1.1", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@opensearch-project/opensearch/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2785,6 +2806,11 @@ "node": ">= 10.0.0" } }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -2913,7 +2939,7 @@ "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", @@ -3314,7 +3340,7 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dependencies": { - "debug": "^3.1.0", + "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" @@ -4224,7 +4250,7 @@ "content-type": "~1.0.4", "cookie": "0.5.0", "cookie-signature": "1.0.6", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -4274,7 +4300,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { - "debug": "^3.1.0", + "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "2.4.1", @@ -4411,7 +4437,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dependencies": { - "debug": "^3.1.0", + "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", @@ -4456,7 +4482,7 @@ "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", "dev": true, "dependencies": { - "json5": "^2.2.2", + "json5": "^0.5.1", "path-exists": "^3.0.0" }, "engines": { @@ -4898,6 +4924,11 @@ "node": ">=0.10.0" } }, + "node_modules/hpagent": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", + "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6150,7 +6181,7 @@ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", "dependencies": { "basic-auth": "~2.0.1", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.0.2" @@ -7296,6 +7327,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -7309,7 +7345,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", @@ -10165,6 +10201,25 @@ "fastq": "^1.6.0" } }, + "@opensearch-project/opensearch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-1.2.0.tgz", + "integrity": "sha512-bX0aUz5e7rlY1lKz1rFrqnNbl/l1CHvvysYB2Jn+C3WNs7nL6FnQjuxLhGwyRdW9W1bFokDoOVgPMIOi/Nn9/g==", + "requires": { + "aws4": "^1.11.0", + "debug": "^4.3.1", + "hpagent": "^0.1.1", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -10714,6 +10769,11 @@ "xml2js": "0.4.19" } }, + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -10807,7 +10867,7 @@ "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", @@ -10820,8 +10880,7 @@ }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" @@ -11112,15 +11171,14 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "requires": { - "debug": "^3.1.0", + "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" @@ -11804,7 +11862,7 @@ "content-type": "~1.0.4", "cookie": "0.5.0", "cookie-signature": "1.0.6", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -11836,8 +11894,7 @@ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" @@ -11848,7 +11905,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { - "debug": "^3.1.0", + "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "2.4.1", @@ -11963,7 +12020,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { - "debug": "^3.1.0", + "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", @@ -11973,8 +12030,7 @@ }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" @@ -12001,7 +12057,7 @@ "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", "dev": true, "requires": { - "json5": "^2.2.2", + "json5": "^0.5.1", "path-exists": "^3.0.0" } }, @@ -12323,6 +12379,11 @@ "parse-passwd": "^1.0.0" } }, + "hpagent": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", + "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -13256,15 +13317,14 @@ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", "requires": { "basic-auth": "~2.0.1", - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", "on-headers": "~1.0.2" }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" @@ -14112,6 +14172,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -14122,7 +14187,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { - "debug": "^3.1.0", + "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", @@ -14138,8 +14203,7 @@ }, "dependencies": { "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" diff --git a/package.json b/package.json index 1ba23b94..40c0ed14 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "private": true, "dependencies": { "@babel/plugin-proposal-decorators": "^7.22.3", + "@opensearch-project/opensearch": "^1.2.0", "@types/swagger-ui-express": "^4.1.3", "@types/winston": "^2.4.4", "aws-sdk": "^2.1101.0", From 73500e4ef7fa32f69655924a37e39285d7ca705d Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sun, 11 Jun 2023 16:43:47 +0900 Subject: [PATCH 02/15] =?UTF-8?q?Perfume=20=EB=AA=A8=EB=8D=B8=EC=9D=98=20?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/tables/Perfume.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/tables/Perfume.ts b/src/models/tables/Perfume.ts index 35e533fb..cef9c543 100644 --- a/src/models/tables/Perfume.ts +++ b/src/models/tables/Perfume.ts @@ -101,7 +101,7 @@ export class Perfume extends Model { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) - Notes: Note; + Notes: Note[]; @BelongsToMany(() => User, { foreignKey: 'userIdx', @@ -139,5 +139,5 @@ export class Perfume extends Model { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) - perfumeKeywords: JoinPerfumeKeyword[]; + JoinPerfumeKeywords: JoinPerfumeKeyword[]; } From 2a1cc2ee243ffd1ed76d02c31d944070cd492825 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sun, 11 Jun 2023 16:44:19 +0900 Subject: [PATCH 03/15] =?UTF-8?q?opensearch=20client=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/opensearch/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/utils/opensearch/index.ts diff --git a/src/utils/opensearch/index.ts b/src/utils/opensearch/index.ts new file mode 100644 index 00000000..d9714b6b --- /dev/null +++ b/src/utils/opensearch/index.ts @@ -0,0 +1,11 @@ +import { Client } from '@opensearch-project/opensearch'; + +const account = process.env.OPENSEARCH_ACCOUNT; +const password = process.env.OPENSEARCH_PASSWORD; +const host = process.env.OPENSEARCH_HOST; +const openSearchNode = `https://${account}:${password}@${host}`; +export const client = new Client({ node: openSearchNode }); + +export const opensearch_index_name = `scentsnote-perfume-${ + process.env.NODE_ENV === 'production' ? 'prod' : 'dev' +}`; From 388a0139f4f2581889657549fa4f179970a2b3c6 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Fri, 16 Jun 2023 16:10:04 +0900 Subject: [PATCH 04/15] =?UTF-8?q?isLikeJob=20=ED=95=A8=EC=88=98=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/LikePerfumeService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/service/LikePerfumeService.ts b/src/service/LikePerfumeService.ts index 75077cef..7196642c 100644 --- a/src/service/LikePerfumeService.ts +++ b/src/service/LikePerfumeService.ts @@ -1,4 +1,5 @@ import LikePerfumeDao from '@src/dao/LikePerfumeDao'; +import { LikePerfume } from '@src/models'; import { FailedToCreateError, NotMatchedError } from '@src/utils/errors/errors'; import _ from 'lodash'; @@ -44,7 +45,7 @@ export class LikePerfumeService { } } - isLikeJob(likePerfumeList: any[]): (obj: any) => any { + isLikeJob(likePerfumeList: LikePerfume[]): (obj: any) => any { const likeMap: { [key: string]: boolean } = _.chain(likePerfumeList) .keyBy('perfumeIdx') .mapValues(() => true) From 0e6bf543e1f8244e420cbf59c8069dd9a7a46fe1 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 11:39:20 +0900 Subject: [PATCH 05/15] =?UTF-8?q?requestPerfumeSearch=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/opensearch/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils/opensearch/index.ts b/src/utils/opensearch/index.ts index d9714b6b..2a5244ce 100644 --- a/src/utils/opensearch/index.ts +++ b/src/utils/opensearch/index.ts @@ -1,4 +1,5 @@ import { Client } from '@opensearch-project/opensearch'; +import { RequestBody } from '@opensearch-project/opensearch/lib/Transport'; const account = process.env.OPENSEARCH_ACCOUNT; const password = process.env.OPENSEARCH_PASSWORD; @@ -9,3 +10,10 @@ export const client = new Client({ node: openSearchNode }); export const opensearch_index_name = `scentsnote-perfume-${ process.env.NODE_ENV === 'production' ? 'prod' : 'dev' }`; + +export async function requestPerfumeSearch(body: RequestBody) { + if (process.env.NODE_ENV === 'test') { + throw new Error('must be mocked'); + } + return await client.search({ index: opensearch_index_name, body }); +} From 57a68404325a2f1fa5239449c2ede0760e836c13 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 11:39:31 +0900 Subject: [PATCH 06/15] =?UTF-8?q?SearchService=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/SearchService.ts | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/service/SearchService.ts diff --git a/src/service/SearchService.ts b/src/service/SearchService.ts new file mode 100644 index 00000000..9dc8183b --- /dev/null +++ b/src/service/SearchService.ts @@ -0,0 +1,96 @@ +import { PerfumeResponse } from '@src/controllers/definitions/response'; +import { ListAndCountDTO, PagingDTO, PerfumeSearchDTO } from '@src/data/dto'; +import { requestPerfumeSearch } from '@utils/opensearch'; + +export default class SearchService { + async searchPerfume( + searchDTO: PerfumeSearchDTO, + pagingDTO: PagingDTO + ): Promise> { + const query = this.buildQuery(searchDTO, pagingDTO); + + const result = await requestPerfumeSearch({ + query, + _source: ['perfumeIdx', 'name', 'Brand.name', 'imageUrl'], + size: pagingDTO.limit, + sort: [{ _doc: { order: 'asc' } }], + }); + const { + body: { hits }, + } = result; + + return new ListAndCountDTO( + hits.total.value, + hits.hits.map(this.transformBody) + ); + } + + private transformBody(body: any) { + return { + ...body._source, + brandName: body._source.Brand.name, + }; + } + + private buildQuery(searchDTO: PerfumeSearchDTO, pagingDTO: PagingDTO) { + const filter = this.buildBoolFilter(searchDTO); + const must = this.buildBoolMust(searchDTO, pagingDTO); + const bool = { + ...(filter && { filter }), + ...(must && { must }), + }; + + return { bool }; + } + + private buildBoolFilter(searchDTO: PerfumeSearchDTO) { + const filter = []; + const { keywordIdxList, brandIdxList, ingredientCategoryList } = + searchDTO; + if (keywordIdxList && keywordIdxList.length > 0) { + filter.push({ + terms: { KeywordIdxList: keywordIdxList }, + }); + } + if (brandIdxList && brandIdxList.length > 0) { + filter.push({ + terms: { 'Brand.brandIdx': brandIdxList }, + }); + } + if (ingredientCategoryList && ingredientCategoryList.length > 0) { + filter.push({ + terms: { 'Ingredients.ingredientIdx': ingredientCategoryList }, + }); + } + if (filter.length > 0) { + return filter; + } + return undefined; + } + + private buildBoolMust(searchDTO: PerfumeSearchDTO, pagingDTO: PagingDTO) { + const { searchText } = searchDTO; + const { offset } = pagingDTO; + const must = []; + if (searchText) { + must.push({ + multi_match: { + query: searchText, + fields: [ + 'name', + 'englishName', + 'Brand.name', + 'Brand.englishName', + ], + }, + }); + } + if (offset) { + must.push({ range: { perfumeIdx: { gte: offset } } }); + } + if (must.length > 0) { + return must; + } + return undefined; + } +} From 7eb6e74f12b5dffe0cac0917cb8a7d590cbd68d1 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 12:18:24 +0900 Subject: [PATCH 07/15] =?UTF-8?q?PerfumeService=20=EC=97=90=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=EC=BD=94=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/PerfumeService.ts | 77 ++++++++--------------------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/src/service/PerfumeService.ts b/src/service/PerfumeService.ts index 72268af4..57753ac4 100644 --- a/src/service/PerfumeService.ts +++ b/src/service/PerfumeService.ts @@ -12,15 +12,12 @@ import { PerfumeDTO, PerfumeIntegralDTO, PerfumeSearchDTO, - PerfumeSearchResultDTO, PerfumeSummaryDTO, PerfumeThumbDTO, PerfumeThumbKeywordDTO, PerfumeThumbWithReviewDTO, UserDTO, } from '@dto/index'; -import { Ingredient } from '@sequelize'; -import IngredientDao from '@src/dao/IngredientDao'; import _ from 'lodash'; import fp from 'lodash/fp'; import { Op } from 'sequelize'; @@ -28,14 +25,16 @@ import { NoteService } from './NoteService'; import { LikePerfumeService } from './LikePerfumeService'; import ImageService from './ImageService'; import KeywordService from './KeywordService'; +import SearchService from './SearchService'; +import { PerfumeResponse } from '@src/controllers/definitions/response'; const LOG_TAG: string = '[Perfume/Service]'; const DEFAULT_VALUE_OF_INDEX = 0; let perfumeDao: PerfumeDao = new PerfumeDao(); -let ingredientDao: IngredientDao = new IngredientDao(); let reviewDao: ReviewDao = new ReviewDao(); let userDao: UserDao = new UserDao(); +let searchService = new SearchService(); export const commonJob = [ removeKeyJob( @@ -124,65 +123,23 @@ class PerfumeService { ); } - /** - * 향수 검색 - * - * @param {PerfumeSearchDTO} perfumeSearchDTO - * @param {PagingDTO} pagingDTO - * @returns {Promise} - **/ async searchPerfume( perfumeSearchDTO: PerfumeSearchDTO, pagingDTO: PagingDTO - ): Promise> { - logger.debug( - `${LOG_TAG} searchPerfume(perfumeSearchDTO = ${perfumeSearchDTO}, pagingDTO = ${pagingDTO})` + ): Promise> { + const result = await searchService.searchPerfume( + perfumeSearchDTO, + pagingDTO ); - return ingredientDao - .getIngredientIdxByCategories( - perfumeSearchDTO.ingredientCategoryList - ) - .then((ingredientList: Ingredient[]) => { - return ingredientList.map((it) => it.ingredientIdx); - }) - .then((ingredientIdxList: number[]) => { - return perfumeDao.search( - perfumeSearchDTO.brandIdxList, - ingredientIdxList, - perfumeSearchDTO.ingredientCategoryList, - perfumeSearchDTO.keywordIdxList, - perfumeSearchDTO.searchText, - pagingDTO - ); - }) - - .then( - async ( - result: ListAndCountDTO - ): Promise> => { - const perfumeIdxList: number[] = result.rows.map( - (it) => it.perfumeIdx - ); - const likePerfumeList: any[] = - await this.likePerfumeService.readLikeInfo( - perfumeSearchDTO.userIdx, - perfumeIdxList - ); - return result.convertType( - ( - item: PerfumeSearchResultDTO - ): PerfumeSearchResultDTO => { - return fp.compose( - ...commonJob, - this.likePerfumeService.isLikeJob( - likePerfumeList - ), - PerfumeSearchResultDTO.createByJson - )(item); - } - ); - } + const perfumeIdxList: number[] = result.rows.map((it) => it.perfumeIdx); + const likePerfumeList: any[] = + await this.likePerfumeService.readLikeInfo( + perfumeSearchDTO.userIdx, + perfumeIdxList ); + return result.convertType((item: PerfumeResponse): PerfumeResponse => { + return this.likePerfumeService.isLikeJob(likePerfumeList)(item); + }); } /** @@ -472,9 +429,7 @@ class PerfumeService { result: ListAndCountDTO, userIdx: number = -1 ): Promise> { - const perfumeIdxList: number[] = result.rows.map( - (it: PerfumeThumbDTO) => it.perfumeIdx - ); + const perfumeIdxList = result.rows.map((it) => it.perfumeIdx); const converter: (item: PerfumeThumbDTO) => PerfumeThumbKeywordDTO = await this.keywordService.getPerfumeThumbKeywordConverter( perfumeIdxList, From 55e26cc9044fbbfaba8500f8ee8dccd5e5379f4b Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 12:19:32 +0900 Subject: [PATCH 08/15] =?UTF-8?q?dto,=20request=EC=97=90=20nullable=20?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/definitions/request/perfume.ts | 12 ++++++------ src/data/dto/PerfumeSearchDTO.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/controllers/definitions/request/perfume.ts b/src/controllers/definitions/request/perfume.ts index f6d2dc6b..06cd474d 100644 --- a/src/controllers/definitions/request/perfume.ts +++ b/src/controllers/definitions/request/perfume.ts @@ -23,14 +23,14 @@ import { PerfumeSearchDTO } from '@src/data/dto'; * type: integer * */ class PerfumeSearchRequest { - readonly keywordList: number[]; - readonly brandList: number[]; - readonly ingredientList: number[]; + readonly keywordList?: number[]; + readonly brandList?: number[]; + readonly ingredientList?: number[]; readonly searchText: string; constructor( - keywordList: number[], - brandList: number[], - ingredientList: number[], + keywordList: number[] | undefined, + brandList: number[] | undefined, + ingredientList: number[] | undefined, searchText: string ) { this.keywordList = keywordList; diff --git a/src/data/dto/PerfumeSearchDTO.ts b/src/data/dto/PerfumeSearchDTO.ts index 32abd41d..d2bb37fd 100644 --- a/src/data/dto/PerfumeSearchDTO.ts +++ b/src/data/dto/PerfumeSearchDTO.ts @@ -1,13 +1,13 @@ class PerfumeSearchDTO { - readonly keywordIdxList: number[]; - readonly brandIdxList: number[]; - readonly ingredientCategoryList: number[]; + readonly keywordIdxList?: number[]; + readonly brandIdxList?: number[]; + readonly ingredientCategoryList?: number[]; readonly searchText: string; readonly userIdx: number; constructor( - keywordIdxList: number[], - brandIdxList: number[], - ingredientCategoryList: number[], + keywordIdxList: number[] | undefined, + brandIdxList: number[] | undefined, + ingredientCategoryList: number[] | undefined, searchText: string, userIdx: number ) { From 6d4295cd2d8bf717f90b221d162e8f3c16838119 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 12:36:00 +0900 Subject: [PATCH 09/15] =?UTF-8?q?controller=EC=97=90=20=EC=8B=A0=EA=B7=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/Perfume.ts | 57 +++++++++++++++----------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/controllers/Perfume.ts b/src/controllers/Perfume.ts index ba1fa750..c4d41811 100644 --- a/src/controllers/Perfume.ts +++ b/src/controllers/Perfume.ts @@ -36,11 +36,9 @@ import { ResponseDTO, SimpleResponseDTO } from '@response/index'; import { PerfumeIntegralDTO, ListAndCountDTO, - PerfumeSearchResultDTO, PerfumeThumbDTO, PerfumeThumbWithReviewDTO, PerfumeThumbKeywordDTO, - PerfumeSearchDTO, PagingDTO, } from '@dto/index'; import { GenderMap } from '@src/utils/enumType'; @@ -194,41 +192,32 @@ const getPerfume: RequestHandler = ( * description: Token is missing or invalid * x-swagger-router-controller: Perfume * */ -const searchPerfume: RequestHandler = ( +const searchPerfume: RequestHandler = async ( req: Request | any, res: Response, next: NextFunction -): any => { - const loginUserIdx: number = req.middlewareToken.loginUserIdx || -1; - const perfumeSearchRequest: PerfumeSearchRequest = - PerfumeSearchRequest.createByJson(req.body); - const pagingRequestDTO: PagingRequestDTO = PagingRequestDTO.createByJson( - req.query - ); - logger.debug( - `${LOG_TAG} likePerfume(userIdx = ${loginUserIdx}, query = ${JSON.stringify( - req.query - )}, body = ${JSON.stringify(req.body)})` - ); - const perfumeSearchDTO: PerfumeSearchDTO = - perfumeSearchRequest.toPerfumeSearchDTO(loginUserIdx); - Perfume.searchPerfume(perfumeSearchDTO, pagingRequestDTO.toPageDTO()) - .then((result: ListAndCountDTO) => { - return result.convertType(PerfumeResponse.createByJson); - }) - .then((response: ListAndCountDTO) => { - LoggerHelper.logTruncated( - logger.debug, - `${LOG_TAG} searchPerfume's result = ${response}` - ); - res.status(StatusCode.OK).json( - new ResponseDTO>( - MSG_GET_SEARCH_PERFUME_SUCCESS, - response - ) - ); - }) - .catch((err: Error) => next(err)); +) => { + const loginUserIdx = req.middlewareToken.loginUserIdx; + const perfumeSearchDTO = PerfumeSearchRequest.createByJson( + req.body + ).toPerfumeSearchDTO(loginUserIdx); + const pagingRequestDTO = PagingRequestDTO.createByJson(req.query); + + try { + const result = await Perfume.searchPerfume( + perfumeSearchDTO, + pagingRequestDTO.toPageDTO() + ); + const response = result.convertType(PerfumeResponse.createByJson); + res.status(StatusCode.OK).json( + new ResponseDTO>( + MSG_GET_SEARCH_PERFUME_SUCCESS, + response + ) + ); + } catch (err: Error | any) { + next(err); + } }; /** From f0a62c41722d52ef8eb2b646a837075cfc890e43 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 12:36:14 +0900 Subject: [PATCH 10/15] =?UTF-8?q?=EB=B6=88=EC=9A=A9=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dao/PerfumeDao.ts | 141 ------------------------- src/data/dto/PerfumeSearchResultDTO.ts | 56 ---------- src/data/dto/index.ts | 1 - 3 files changed, 198 deletions(-) delete mode 100644 src/data/dto/PerfumeSearchResultDTO.ts diff --git a/src/dao/PerfumeDao.ts b/src/dao/PerfumeDao.ts index c01c5a07..311e4496 100644 --- a/src/dao/PerfumeDao.ts +++ b/src/dao/PerfumeDao.ts @@ -9,7 +9,6 @@ import { PagingDTO, PerfumeDTO, PerfumeInquireHistoryDTO, - PerfumeSearchResultDTO, PerfumeThumbDTO, } from '@dto/index'; @@ -38,36 +37,6 @@ const PERFUME_THUMB_COLUMNS: string[] = [ import redis from '@utils/db/redis'; -const SQL_SEARCH_PERFUME_SELECT: string = - 'SELECT ' + - 'p.perfume_idx AS perfumeIdx, p.brand_idx AS brandIdx, p.name, p.english_name AS englishName, p.image_url AS imageUrl, p.created_at AS createdAt, p.updated_at AS updatedAt, ' + - 'b.brand_idx AS "Brand.brandIdx", ' + - 'b.name AS "Brand.name", ' + - 'b.english_name AS "Brand.englishName", ' + - 'b.first_initial AS "Brand.firstInitial", ' + - 'b.image_url AS "Brand.imageUrl", ' + - 'b.description AS "Brand.description", ' + - 'b.created_at AS "Brand.createdAt", ' + - 'b.updated_at AS "Brand.updatedAt" ' + - 'FROM perfumes p ' + - 'INNER JOIN brands b ON p.brand_idx = b.brand_idx ' + - 'WHERE p.deleted_at IS NULL AND (:whereCondition) ' + - 'ORDER BY :orderCondition ' + - 'LIMIT :limit ' + - 'OFFSET :offset'; -const SQL_SEARCH_BRAND_CONDITION: string = ' p.brand_idx IN (:brands)'; -const SQL_SEARCH_KEYWORD_CONDITION: string = - '(SELECT COUNT(DISTINCT(jpk.keyword_idx)) FROM join_perfume_keywords jpk WHERE jpk.perfume_idx = p.perfume_idx AND jpk.keyword_idx IN (:keywords) GROUP BY jpk.perfume_idx) = (:keywordCount) '; -const SQL_SEARCH_INGREDIENT_CONDITION: string = - '(SELECT COUNT(DISTINCT(i.category_idx)) FROM notes n INNER JOIN ingredients i ON i.ingredient_idx = n.ingredient_idx WHERE n.perfume_idx = p.perfume_idx AND n.ingredient_idx IN (:ingredients) GROUP BY n.perfume_idx) = (:categoryCount) '; - -const SQL_SEARCH_PERFUME_SELECT_COUNT: string = - 'SELECT ' + - 'COUNT(p.perfume_idx) as count ' + - 'FROM perfumes p ' + - 'INNER JOIN brands b ON p.brand_idx = b.brand_idx ' + - 'WHERE p.deleted_at IS NULL AND (:whereCondition) '; - const SQL_SEARCH_RANDOM_PERFUME_WITH_MIN_REVIEW_COUNT: string = 'SELECT ' + '`Perfume`.perfume_idx AS perfumeIdx, `Perfume`.brand_idx AS brandIdx, `Perfume`.name, `Perfume`.english_name AS englishName, `Perfume`.image_url AS imageUrl, `Perfume`.created_at AS createdAt, `Perfume`.updated_at AS updatedAt, ' + @@ -97,116 +66,6 @@ const defaultOption: { [key: string]: any } = { }; class PerfumeDao { - /** - * 향수 검색 - * - * @param {number[]} brandIdxList - * @param {number[]} ingredientIdxList - * @param {number[]} categoryIdxList - * @param {number[]} keywordIdxList - * @param {string} searchText - * @param {PagingDTO} pagingDTO - * @returns {Promise} perfumeList - */ - async search( - brandIdxList: number[], - ingredientIdxList: number[], - categoryIdxList: number[], - keywordIdxList: number[], - searchText: string, - pagingDTO: PagingDTO - ): Promise> { - logger.debug( - `${LOG_TAG} search(brandIdxList = ${brandIdxList}, ` + - `ingredientIdxList = ${ingredientIdxList}, ` + - `keywordList = ${keywordIdxList}, ` + - `searchText = ${searchText}, ` + - `pagingDTO = ${pagingDTO}` - ); - let orderCondition = pagingDTO.sqlOrderQuery('p.perfume_idx ASC'); - - let whereCondition: string = ''; - if ( - ingredientIdxList.length + - keywordIdxList.length + - brandIdxList.length > - 0 - ) { - const arr: number[][] = [ - ingredientIdxList, - keywordIdxList, - brandIdxList, - ]; - const conditionSQL: string[] = [ - SQL_SEARCH_INGREDIENT_CONDITION, - SQL_SEARCH_KEYWORD_CONDITION, - SQL_SEARCH_BRAND_CONDITION, - ]; - whereCondition = arr - .reduce((prev: string[], cur: number[], index: number) => { - if (cur.length > 0) { - prev.push(conditionSQL[index]); - } - return prev; - }, []) - .join(' AND '); - } - if (searchText && searchText.length > 0) { - if (whereCondition.length) { - whereCondition = `${whereCondition} AND `; - } - whereCondition = `${whereCondition} (MATCH(p.name, p.english_name) AGAINST('${searchText}*' IN BOOLEAN MODE)`; - if (brandIdxList.length == 0) { - whereCondition = `${whereCondition} OR (MATCH(b.name, b.english_name) AGAINST ('${searchText}*' IN BOOLEAN MODE))`; - } - whereCondition = `${whereCondition} )`; - orderCondition = - `case when MATCH(p.name, p.english_name) AGAINST('${searchText}') then 0 ` + - `when MATCH(p.name, p.english_name) AGAINST('${searchText}*' IN BOOLEAN MODE) then 1 ` + - `else 2 end, ${orderCondition}`; - } - const countSQL: string = SQL_SEARCH_PERFUME_SELECT_COUNT.replace( - ':whereCondition', - whereCondition.length > 0 ? whereCondition : 'TRUE' - ); - const selectSQL: string = SQL_SEARCH_PERFUME_SELECT.replace( - ':whereCondition', - whereCondition.length > 0 ? whereCondition : 'TRUE' - ).replace(':orderCondition', orderCondition); - - if (ingredientIdxList.length == 0) ingredientIdxList.push(-1); - if (brandIdxList.length == 0) brandIdxList.push(-1); - if (keywordIdxList.length == 0) keywordIdxList.push(-1); - const [{ count }] = await sequelize.query<{ count: number }>(countSQL, { - replacements: { - keywords: keywordIdxList, - brands: brandIdxList, - ingredients: ingredientIdxList, - categoryCount: categoryIdxList.length, - keywordCount: keywordIdxList.length, - }, - type: QueryTypes.SELECT, - raw: true, - }); - const rows: PerfumeSearchResultDTO[] = ( - await sequelize.query(selectSQL, { - replacements: { - keywords: keywordIdxList, - brands: brandIdxList, - ingredients: ingredientIdxList, - categoryCount: categoryIdxList.length, - keywordCount: keywordIdxList.length, - limit: pagingDTO.limit, - offset: pagingDTO.offset, - }, - type: QueryTypes.SELECT, - raw: true, - nest: true, - }) - ).map(PerfumeSearchResultDTO.createByJson); - return new ListAndCountDTO(count, rows); - } - /** * 향수 조회 * diff --git a/src/data/dto/PerfumeSearchResultDTO.ts b/src/data/dto/PerfumeSearchResultDTO.ts deleted file mode 100644 index b9f5429c..00000000 --- a/src/data/dto/PerfumeSearchResultDTO.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { BrandDTO } from '@dto/BrandDTO'; - -class PerfumeSearchResultDTO { - readonly perfumeIdx: number; - readonly name: string; - readonly englishName: string; - readonly brandName: string; - readonly isLiked: boolean; - readonly imageUrl: string; - readonly Brand: BrandDTO; - readonly brandIdx: number; - readonly createdAt: Date; - readonly updatedAt: Date; - constructor( - perfumeIdx: number, - name: string, - englishName: string, - isLiked: boolean, - imageUrl: string, - Brand: BrandDTO, - brandIdx: number, - createdAt: Date, - updatedAt: Date - ) { - this.perfumeIdx = perfumeIdx; - this.name = name; - this.englishName = englishName; - this.brandName = Brand.name; - this.isLiked = isLiked || false; - this.imageUrl = imageUrl; - this.Brand = Brand; - this.brandIdx = brandIdx; - this.createdAt = createdAt; - this.updatedAt = updatedAt; - } - - public toString(): string { - return `${this.constructor.name} (${JSON.stringify(this)})`; - } - - static createByJson(json: any): PerfumeSearchResultDTO { - return new PerfumeSearchResultDTO( - json.perfumeIdx, - json.name, - json.englishName, - json.isLiked, - json.imageUrl, - BrandDTO.createByJson(json.Brand), - json.brandIdx, - json.createdAt, - json.updatedAt - ); - } -} - -export { PerfumeSearchResultDTO }; diff --git a/src/data/dto/index.ts b/src/data/dto/index.ts index 6bf4a425..69fff62a 100644 --- a/src/data/dto/index.ts +++ b/src/data/dto/index.ts @@ -11,7 +11,6 @@ export * from './PerfumeDTO'; export * from './PerfumeIntegralDTO'; export * from './PerfumeSearchDTO'; export * from './PerfumeInquireHistoryDTO'; -export * from './PerfumeSearchResultDTO'; export * from './PerfumeSummaryDTO'; export * from './PerfumeThumbDTO'; export * from './PerfumeThumbWithReviewDTO'; From 97109f90366939db52385e35a605c2981042b02b Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Sat, 24 Jun 2023 12:37:48 +0900 Subject: [PATCH 11/15] package-lock.json --- package-lock.json | 169 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 127 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3fb92f6c..8ee319bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2956,13 +2956,18 @@ } }, "node_modules/body-parser/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3350,13 +3355,18 @@ } }, "node_modules/connect/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4288,11 +4298,11 @@ } }, "node_modules/express/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "node_modules/express/node_modules/finalhandler": { @@ -4312,6 +4322,11 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4450,13 +4465,18 @@ } }, "node_modules/finalhandler/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/finalhandler/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -4489,6 +4509,15 @@ "node": ">=4.0.0" } }, + "node_modules/find-babel-config/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -6191,13 +6220,18 @@ } }, "node_modules/morgan/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/morgan/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7364,13 +7398,18 @@ } }, "node_modules/send/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10880,11 +10919,17 @@ }, "dependencies": { "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -11178,11 +11223,17 @@ }, "dependencies": { "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -11894,10 +11945,11 @@ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, "finalhandler": { @@ -11914,6 +11966,11 @@ "unpipe": "~1.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -12030,12 +12087,18 @@ }, "dependencies": { "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -12059,6 +12122,14 @@ "requires": { "json5": "^0.5.1", "path-exists": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", + "dev": true + } } }, "find-cache-dir": { @@ -13324,12 +13395,18 @@ }, "dependencies": { "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -14203,10 +14280,18 @@ }, "dependencies": { "debug": { - "version": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { - "ms": "^2.1.1" + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } } }, "ms": { From 7c0ca23ee8b8349e4cdaa9759415b957960fd6df Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Tue, 27 Jun 2023 21:07:54 +0900 Subject: [PATCH 12/15] =?UTF-8?q?=EA=B2=80=EC=83=89=EC=97=94=EC=A7=84=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=95=A8=EC=88=98=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/service/SearchService.ts | 16 ++++++++++++++-- src/utils/opensearch/index.ts | 13 ++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/service/SearchService.ts b/src/service/SearchService.ts index 9dc8183b..bf6a0b82 100644 --- a/src/service/SearchService.ts +++ b/src/service/SearchService.ts @@ -2,6 +2,16 @@ import { PerfumeResponse } from '@src/controllers/definitions/response'; import { ListAndCountDTO, PagingDTO, PerfumeSearchDTO } from '@src/data/dto'; import { requestPerfumeSearch } from '@utils/opensearch'; +interface PerfumeSearchResultItem { + perfumeIdx: number; + name: string; + Brand: { + name: string; + }; + imageUrl: string; + isLiked: boolean; +} + export default class SearchService { async searchPerfume( searchDTO: PerfumeSearchDTO, @@ -9,7 +19,7 @@ export default class SearchService { ): Promise> { const query = this.buildQuery(searchDTO, pagingDTO); - const result = await requestPerfumeSearch({ + const result = await requestPerfumeSearch({ query, _source: ['perfumeIdx', 'name', 'Brand.name', 'imageUrl'], size: pagingDTO.limit, @@ -25,7 +35,9 @@ export default class SearchService { ); } - private transformBody(body: any) { + private transformBody(body: { + _source: PerfumeSearchResultItem; + }): PerfumeResponse { return { ...body._source, brandName: body._source.Brand.name, diff --git a/src/utils/opensearch/index.ts b/src/utils/opensearch/index.ts index 2a5244ce..54919aad 100644 --- a/src/utils/opensearch/index.ts +++ b/src/utils/opensearch/index.ts @@ -11,7 +11,18 @@ export const opensearch_index_name = `scentsnote-perfume-${ process.env.NODE_ENV === 'production' ? 'prod' : 'dev' }`; -export async function requestPerfumeSearch(body: RequestBody) { +interface SearchRequestResult { + body: { + hits: { + total: { value: number }; + hits: Array<{ _source: T }>; + }; + }; +} + +export async function requestPerfumeSearch( + body: RequestBody +): Promise> { if (process.env.NODE_ENV === 'test') { throw new Error('must be mocked'); } From 3a888272851bba8c9d4b16b86be0ea70dbd38469 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Tue, 27 Jun 2023 21:08:22 +0900 Subject: [PATCH 13/15] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EC=97=90=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 295 ++++++++++++++++++ package.json | 2 + tests/test_integral/perfume.spec.ts | 57 ++-- .../test_unit/service/PerfumeService.spec.ts | 21 +- 4 files changed, 351 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ee319bc..9ae7d81b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "@types/morgan": "^1.9.3", "@types/node": "^17.0.45", "@types/node-cron": "^3.0.2", + "@types/sinon": "^10.0.15", "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/validator": "^13.7.17", @@ -71,6 +72,7 @@ "mocha": "^10.2.0", "nodemon": "^2.0.22", "sequelize-cli": "^6.4.1", + "sinon": "^15.2.0", "supertest": "^6.2.2", "swagger-jsdoc": "^6.1.0", "swagger-ui-express": "^4.3.0", @@ -2103,6 +2105,50 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2315,6 +2361,21 @@ "@types/node": "*" } }, + "node_modules/@types/sinon": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", + "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/superagent": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.15.tgz", @@ -5595,6 +5656,12 @@ "node": ">=10" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -6375,6 +6442,43 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, "node_modules/node-cron": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", @@ -7678,6 +7782,54 @@ "semver": "bin/semver.js" } }, + "node_modules/sinon": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -10259,6 +10411,52 @@ } } }, + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -10469,6 +10667,21 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", + "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/superagent": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.15.tgz", @@ -12901,6 +13114,12 @@ } } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -13525,6 +13744,45 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "nise": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", + "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-cron": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", @@ -14480,6 +14738,43 @@ } } }, + "sinon": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", + "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", diff --git a/package.json b/package.json index 40c0ed14..75b461c8 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@types/morgan": "^1.9.3", "@types/node": "^17.0.45", "@types/node-cron": "^3.0.2", + "@types/sinon": "^10.0.15", "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/validator": "^13.7.17", @@ -85,6 +86,7 @@ "mocha": "^10.2.0", "nodemon": "^2.0.22", "sequelize-cli": "^6.4.1", + "sinon": "^15.2.0", "supertest": "^6.2.2", "swagger-jsdoc": "^6.1.0", "swagger-ui-express": "^4.3.0", diff --git a/tests/test_integral/perfume.spec.ts b/tests/test_integral/perfume.spec.ts index ecc86d9d..fb2ea2f3 100644 --- a/tests/test_integral/perfume.spec.ts +++ b/tests/test_integral/perfume.spec.ts @@ -3,8 +3,10 @@ import { Done } from 'mocha'; import request from 'supertest'; import { expect } from 'chai'; dotenv.config(); +import sinon from 'sinon'; import StatusCode from '@utils/statusCode'; +import * as opensearch from '@utils/opensearch'; import { MSG_GET_PERFUME_DETAIL_SUCCESS, @@ -46,6 +48,16 @@ import UserService from '@services/UserService'; import { encrypt } from '@libs/crypto'; describe('# Perfume Integral Test', () => { + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + describe('# with Token Test', () => { var user1token: string; var user1idx: number; @@ -270,7 +282,12 @@ describe('# Perfume Integral Test', () => { .catch((err: Error) => done(err)); }); describe('# searchPerfume Test', () => { - const tests: any[] = [ + beforeEach(() => { + sandbox.stub(opensearch, 'requestPerfumeSearch').resolves({ + body: { hits: { total: { value: 1 }, hits: [] } }, + }); + }); + const tests = [ { args: { searchText: 'Tom', @@ -291,27 +308,23 @@ describe('# Perfume Integral Test', () => { }, ]; let i = 0; - tests.forEach( - ({ args, expected }: { args: any; expected: any }) => { - it(`searchPerfumeTest case ${i++}`, (done: Done) => { - request(app) - .post(`${basePath}/perfume/search`) - .send(args) - .expect((res: request.Response) => { - expect(res.status).to.be.eq(StatusCode.OK); - const result: ResponseDTO< - ListAndCountDTO - > = res.body; - expect(result.message).to.be.eq( - MSG_GET_SEARCH_PERFUME_SUCCESS - ); - expect(result.data!!.count).to.be.gte(expected); - done(); - }) - .catch((err: Error) => done(err)); - }); - } - ); + tests.forEach(({ args, expected }) => { + it(`searchPerfumeTest case ${i++}`, () => { + request(app) + .post(`${basePath}/perfume/search`) + .send(args) + .expect((res: request.Response) => { + expect(res.status).to.be.eq(StatusCode.OK); + const result: ResponseDTO< + ListAndCountDTO + > = res.body; + expect(result.message).to.be.eq( + MSG_GET_SEARCH_PERFUME_SUCCESS + ); + expect(result.data!!.count).to.be.gte(expected); + }); + }); + }); }); describe('# getNewPerfume Test', () => { it('success case', (done: Done) => { diff --git a/tests/test_unit/service/PerfumeService.spec.ts b/tests/test_unit/service/PerfumeService.spec.ts index 8c36e79c..6800365b 100644 --- a/tests/test_unit/service/PerfumeService.spec.ts +++ b/tests/test_unit/service/PerfumeService.spec.ts @@ -7,13 +7,14 @@ dotenv.config(); import PerfumeService from '@services/PerfumeService'; import { GENDER_MAN, GENDER_WOMAN, GRADE_USER } from '@utils/constants'; +import sinon from 'sinon'; +import * as opensearch from '@utils/opensearch'; import { ListAndCountDTO, PagingDTO, PerfumeIntegralDTO, PerfumeSearchDTO, - PerfumeSearchResultDTO, PerfumeThumbDTO, PerfumeThumbWithReviewDTO, } from '@dto/index'; @@ -50,6 +51,17 @@ describe('# Perfume Service Test', () => { before(async function () { await require('../dao/common/presets.js')(this); }); + + let sandbox: sinon.SinonSandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + describe('# read Test', () => { describe('# read detail Test', () => { it('# success Test', (done: Done) => { @@ -284,6 +296,11 @@ describe('# Perfume Service Test', () => { }); it('# search Test', (done: Done) => { + sandbox + .stub(opensearch, 'requestPerfumeSearch') + .resolves({ + body: { hits: { total: { value: 1 }, hits: [] } }, + }); mockLikePerfumeDao.readLikeInfo = async ( userIdx: number, __: any @@ -298,7 +315,7 @@ describe('# Perfume Service Test', () => { 1 ); Perfume.searchPerfume(perfumeSearchDTO, defaultPagingDTO) - .then((result: ListAndCountDTO) => { + .then((result) => { expect(result).to.be.instanceOf(ListAndCountDTO); done(); }) From d23a835824ec9901c8101c5d8b2ce0db54e8e590 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Tue, 27 Jun 2023 21:08:29 +0900 Subject: [PATCH 14/15] =?UTF-8?q?=EB=B6=88=EC=9A=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_unit/dao/PerfumeDao.spec.ts | 382 ------------------------- 1 file changed, 382 deletions(-) diff --git a/tests/test_unit/dao/PerfumeDao.spec.ts b/tests/test_unit/dao/PerfumeDao.spec.ts index c34e45e1..9f6514b2 100644 --- a/tests/test_unit/dao/PerfumeDao.spec.ts +++ b/tests/test_unit/dao/PerfumeDao.spec.ts @@ -12,7 +12,6 @@ import { PerfumeDTO, ListAndCountDTO, PerfumeThumbDTO, - PerfumeSearchResultDTO, PerfumeInquireHistoryDTO, PagingDTO, } from '@dto/index'; @@ -20,9 +19,7 @@ import { import BrandHelper from '../mock_helper/BrandHelper'; import PerfumeThumbMockHelper from '../mock_helper/PerfumeThumbMockHelper'; import _ from 'lodash'; -import Sequelize, { Op } from 'sequelize'; const perfumeDao = new PerfumeDao(); -import { Note, JoinPerfumeKeyword } from '@sequelize'; const defaultPagingDTO: PagingDTO = PagingDTO.createByJson({}); @@ -53,385 +50,6 @@ describe('# perfumeDao Test', () => { }); }); - describe('# search Test', () => { - it('# success case (integral and brand filter)', (done: Done) => { - const ingredients: number[] = [1, 2, 3, 4, 5]; - const categories: number[] = [1, 2, 3, 4, 5]; - const brands: number[] = [1, 2, 3, 4, 5]; - perfumeDao - .search( - brands, - ingredients, - categories, - [], - '', - PagingDTO.createByJson({ - order: [['createdAt', 'asc']], - }) - ) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(5); - expect(result.rows.length).to.be.eq(5); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.perfumeIdx).to.be.ok; - expect(perfume.brandName).to.be.ok; - expect(perfume.name).to.be.ok; - expect(perfume.imageUrl).to.be.ok; - expect(perfume.createdAt).to.be.not.undefined; - expect(perfume.updatedAt).to.be.not.undefined; - - expect(perfume.Brand.brandIdx).to.be.oneOf( - brands - ); - - BrandHelper.validTest.call(perfume.Brand); - } - ); - - const sortedByDao: string = result.rows - .map((it: PerfumeSearchResultDTO) => - it.createdAt.getTime() - ) - .join(','); - const sortedByJS: string = result.rows - .map((it: PerfumeSearchResultDTO) => - it.createdAt.getTime() - ) - .sort() - .reverse() - .join(','); - expect(sortedByJS).eq(sortedByDao); - - return Promise.all( - result.rows.map((it: PerfumeSearchResultDTO) => { - return Note.findAll({ - where: { - perfumeIdx: it.perfumeIdx, - ingredientIdx: { - [Op.in]: ingredients, - }, - }, - nest: true, - raw: true, - }); - }) - ); - }) - .then((result: any[]) => { - for (const ingredientByPerfumeIdxArr of result) { - expect(ingredientByPerfumeIdxArr.length).to.be.eq( - ingredients.length - ); - for (const ingredient of ingredientByPerfumeIdxArr) { - expect(ingredient.ingredientIdx).to.be.oneOf( - ingredients - ); - } - } - done(); - }) - .catch((err: Error) => done(err)); - }); - it('# success case (empty filter)', (done: Done) => { - perfumeDao - .search([], [], [], [], '', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.gt(3); - expect(result.rows.length).to.be.gt(3); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.perfumeIdx).to.be.ok; - expect(perfume.brandIdx).to.be.ok; - expect(perfume.name).to.be.ok; - expect(perfume.imageUrl).to.be.ok; - expect(perfume.createdAt).to.be.not.undefined; - expect(perfume.updatedAt).to.be.not.undefined; - - BrandHelper.validTest.call(perfume.Brand); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (ingredient filter 1)', (done: Done) => { - const ingredients: number[] = [1, 2, 3, 4, 5]; - const categories: number[] = [1, 2, 3, 4, 5]; - perfumeDao - .search( - [], - ingredients, - categories, - [], - '', - defaultPagingDTO - ) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(5); - expect(result.rows.length).to.eq(5); - return Promise.all( - result.rows.map((it: PerfumeSearchResultDTO) => { - return Note.findAll({ - where: { - perfumeIdx: it.perfumeIdx, - ingredientIdx: { - [Op.in]: ingredients, - }, - }, - raw: true, - nest: true, - }); - }) - ); - }) - .then((result: any[]) => { - for (const ingredientByPerfumeIdxArr of result) { - expect(ingredientByPerfumeIdxArr.length).to.be.eq( - ingredients.length - ); - for (const ingredient of ingredientByPerfumeIdxArr) { - expect(ingredient.ingredientIdx).to.be.oneOf( - ingredients - ); - } - } - result.forEach((it: any) => { - expect(it.length).eq(ingredients.length); - }); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (ingredient filter with no result)', (done: Done) => { - const ingredients: number[] = [1, 2, 3, 4, 5, 6]; - const categories: number[] = [1, 2, 3, 4, 5, 6]; - perfumeDao - .search( - [], - ingredients, - categories, - [], - '', - defaultPagingDTO - ) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(0); - expect(result.rows.length).to.eq(0); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (brand filter)', (done: Done) => { - const brands: number[] = [1, 2, 3, 4]; - perfumeDao - .search(brands, [], [], [], '', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.gte(2); - expect(result.rows.length).to.gte(2); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.brandIdx).to.be.oneOf(brands); - expect(perfume.Brand.brandIdx).to.be.eq( - perfume.brandIdx - ); - BrandHelper.validTest.call(perfume.Brand); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (keyword filter 1)', (done: Done) => { - const keywords: number[] = [1, 3, 5]; - perfumeDao - .search([], [], [], keywords, '', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(1); - expect(result.rows.length).to.eq(1); - return Promise.all( - result.rows.map((it: PerfumeSearchResultDTO) => { - return JoinPerfumeKeyword.findAll({ - where: { - perfumeIdx: it.perfumeIdx, - keywordIdx: { - [Op.in]: keywords, - }, - }, - raw: true, - nest: true, - }); - }) - ); - }) - .then((result: any[]) => { - for (const keywordList of result) { - expect(keywordList.length).to.be.eq( - keywords.length - ); - for (const keyword of keywordList) { - expect(keyword.keywordIdx).to.be.oneOf( - keywords - ); - } - } - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (keyword filter with no result)', (done: Done) => { - const keywords: number[] = [1, 2, 5]; - perfumeDao - .search([], [], [], keywords, '', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(0); - expect(result.rows.length).to.eq(0); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (order by recent)', (done: Done) => { - perfumeDao - .search( - [], - [], - [], - [], - '', - PagingDTO.createByJson({ - order: [['createdAt', 'desc']], - }) - ) - .then((result: ListAndCountDTO) => { - expect(result.rows.length).gte(3); - const sortedByDao = result.rows - .map((it) => it.perfumeIdx) - .join(','); - const sortedByJS: string = result.rows - .sort( - ( - a: PerfumeSearchResultDTO, - b: PerfumeSearchResultDTO - ): number => { - return a.createdAt.getTime() < - b.createdAt.getTime() - ? -1 - : 1; - } - ) - .map((it: PerfumeSearchResultDTO) => it.perfumeIdx) - .join(','); - expect(sortedByDao).eq(sortedByJS); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (order by random) ', (done: Done) => { - const pagingDTO = PagingDTO.createByJson({ - order: [Sequelize.fn('RAND')], - }); - Promise.all([ - perfumeDao.search([], [], [], [], '', pagingDTO), - perfumeDao.search([], [], [], [], '', pagingDTO), - perfumeDao.search([], [], [], [], '', pagingDTO), - ]) - .then(([result1, result2, result3]) => { - expect(result1.rows.length).gte(3); - expect(result2.rows.length).gte(3); - expect(result3.rows.length).gte(3); - const str1: string = result1.rows - .map((it) => it.perfumeIdx) - .join(','); - const str2: string = result2.rows - .map((it) => it.perfumeIdx) - .join(','); - const str3: string = result3.rows - .map((it) => it.perfumeIdx) - .join(','); - expect(str1 == str2 && str1 == str3).eq(false); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (searchText with perfume name)', (done: Done) => { - perfumeDao - .search([], [], [], [], '향수', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(6); - expect(result.rows.length).to.be.eq(6); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.name).to.be.contains('향수'); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (searchText with perfume english_name)', (done: Done) => { - perfumeDao - .search([], [], [], [], 'perfume', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(6); - expect(result.rows.length).to.be.eq(6); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.englishName).to.be.contains( - 'perfume' - ); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (searchText with brand name)', (done: Done) => { - perfumeDao - .search([], [], [], [], '브랜드', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(6); - expect(result.rows.length).to.be.eq(6); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect(perfume.Brand.name).to.be.contains( - '브랜드' - ); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - - it('# success case (searchText with brand english_name)', (done: Done) => { - perfumeDao - .search([], [], [], [], 'brand', defaultPagingDTO) - .then((result: ListAndCountDTO) => { - expect(result.count).to.be.eq(6); - expect(result.rows.length).to.be.eq(6); - result.rows.forEach( - (perfume: PerfumeSearchResultDTO) => { - expect( - perfume.Brand.englishName - ).to.be.contains('brand'); - } - ); - done(); - }) - .catch((err: Error) => done(err)); - }); - }); - describe('# readPerfume Test', () => { it('# read new Perfume', (done: Done) => { perfumeDao From b1a1e8ee076260d1ba6feeb6c0ce73a26f4846a3 Mon Sep 17 00:00:00 2001 From: Jinho Hyeon Date: Tue, 27 Jun 2023 23:04:50 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=EA=B2=80=EC=83=89=EC=97=94=EC=A7=84=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EA=B0=B1=EC=8B=A0=20cron=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schedules/index.ts | 8 ++ src/service/PerfumeSearchRefreshService.ts | 86 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/service/PerfumeSearchRefreshService.ts diff --git a/src/schedules/index.ts b/src/schedules/index.ts index c995b3b6..ece3dd82 100644 --- a/src/schedules/index.ts +++ b/src/schedules/index.ts @@ -3,6 +3,7 @@ import cron from 'node-cron'; import { logger } from '@modules/winston'; import properties from '@src/utils/properties'; import MonitoringService from '@src/service/MonitoringService'; +import { PerfumeSearchRefreshService } from '@src/service/PerfumeSearchRefreshService'; const TAG = '[Scheduler]'; const searchHistoryService = new SearchHistoryService(); @@ -34,6 +35,13 @@ class SchedulerManager { ); sendServerStatusMessage(); }), + cron.schedule('0 0 4 * * *', (now) => { + logger.debug( + TAG, + `execute migratePerfumes() by schedule [0 0 16 * * *] at ${now}` + ); + new PerfumeSearchRefreshService().migratePerfumes(); + }), ]; constructor() { if (debug) { diff --git a/src/service/PerfumeSearchRefreshService.ts b/src/service/PerfumeSearchRefreshService.ts new file mode 100644 index 00000000..2289e3fc --- /dev/null +++ b/src/service/PerfumeSearchRefreshService.ts @@ -0,0 +1,86 @@ +import { + Brand, + Ingredient, + JoinPerfumeKeyword, + Note, + Perfume, +} from '@src/models'; +import { client, opensearch_index_name } from '@utils/opensearch'; + +export class PerfumeSearchRefreshService { + private transformToDoc(perfume: Perfume) { + perfume = perfume.toJSON(); + return { + perfumeIdx: perfume.perfumeIdx, + name: perfume.name, + englishName: perfume.englishName, + imageUrl: perfume.imageUrl, + createdAt: perfume.createdAt, + updatedAt: perfume.updatedAt, + Brand: { + brandIdx: perfume.Brand.brandIdx, + name: perfume.Brand.name, + englishName: perfume.Brand.englishName, + }, + Ingredients: perfume.Notes.map((v) => { + return { + ingredientIdx: v.ingredientIdx, + categoryIdx: v.Ingredients.categoryIdx, + }; + }), + KeywordIdxList: perfume.JoinPerfumeKeywords.map( + (i) => i.keywordIdx + ), + }; + } + + async migratePerfumes() { + const perfumes = await Perfume.findAll({ + where: { + deleted_at: null, + }, + include: [ + { + model: Brand, + as: 'Brand', + }, + { + model: JoinPerfumeKeyword, + as: 'JoinPerfumeKeywords', + }, + { + model: Note, + as: 'Notes', + include: [ + { + model: Ingredient, + as: 'Ingredients', + }, + ], + }, + ], + // raw: true, + nest: true, + }); + const docs = perfumes + .map((i) => { + return [ + { + index: { + _index: opensearch_index_name, + _id: i.perfumeIdx, + }, + }, + this.transformToDoc(i), + ]; + }) + .flat(); + // 기존 문서를 모두 삭제하고, 새로 추가함. + // FIXME: 변경분만 확인후 추가하는 방식으로 개선 필요. + await client.deleteByQuery({ + index: opensearch_index_name, + body: { query: { match_all: {} } }, + }); + await client.bulk({ body: docs, refresh: true }); + } +}