From 6b3ecf67ea70c7796c259c9c021c3f0bb9291cb0 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 6 Feb 2024 21:14:38 +0900 Subject: [PATCH 01/33] =?UTF-8?q?Add:=20=EC=B2=AB=EB=B2=88=EC=A7=B8=20?= =?UTF-8?q?=EA=B3=84=ED=9A=8D=20=EC=BD=94=EB=93=9C=20(=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EC=98=88=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 1 + loadenv.js | 4 ++ src/locations.json | 19 +++++ src/modules/fare.js | 0 src/modules/stores/mongo.js | 13 ++++ src/routes/fare.js | 28 ++++++++ src/services/fare.js | 138 ++++++++++++++++++++++++++++++++++++ 7 files changed, 203 insertions(+) create mode 100644 src/locations.json create mode 100644 src/modules/fare.js create mode 100644 src/routes/fare.js create mode 100644 src/services/fare.js diff --git a/app.js b/app.js index a26c4b46..dcab200b 100644 --- a/app.js +++ b/app.js @@ -69,6 +69,7 @@ app.use("/chats", require("./src/routes/chats")); app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); +app.use("/fare", require("./src/routes/fare")); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/loadenv.js b/loadenv.js index 789e21db..64b57b19 100644 --- a/loadenv.js +++ b/loadenv.js @@ -44,4 +44,8 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional + + // Naver Cloud Platform Maps Directions 5 API Keys + naverCloudApiId: process.env.NAVER_MAP_API_ID, //required + naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //required }; diff --git a/src/locations.json b/src/locations.json new file mode 100644 index 00000000..73e5cc21 --- /dev/null +++ b/src/locations.json @@ -0,0 +1,19 @@ +{ + "카이스트 본원": "127.357913,36.373724", + "카이스트 문지캠퍼스": "127.396729,36.393039", + "대전역": "127.432885,36.331373", + "서대전역": "127.404002,36.322479", + "대전복합터미널": "127.436362,36.351862", + "유성 고속버스터미널": "127.336115,36.359945", + "유성 시외버스터미널": "127.330246,36.356030", + "대전청사 고속버스터미널": "127.388202,36.361294", + "대전청사 시외버스터미널": "127.377567,36.361290", + "갤러리아 타임월드": "127.378191,36.352513", + "궁동 로데오거리": "127.349825,36.360342", + "만년중학교": "127.3757119,36.366771", + "신세계백화점": "127.382517,36.3779922", + "월평역": "127.368200,36.3580706", + "유성구청": "127.355899,36.362081", + "유성구 예비군 훈련장": "127.341971,36.393751" + } + \ No newline at end of file diff --git a/src/modules/fare.js b/src/modules/fare.js new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 695845fc..0000e74c 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -172,6 +172,18 @@ const adminLogSchema = Schema({ }, // 수행 업무 }); +const taxiFareSchema = Schema( + { + start: { type: String, required: true }, // 출발지 + goal: { type: String, required: true }, // 도착지 + time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리, 0 ~ 47 (0:00 ~ 23:30)) + fare: { type: Number, default: false }, // 예상 택시 요금 + }, + { + timestamps: true, // 최근 업데이트 시간 기록용 + } +); + mongoose.set("strictQuery", true); const database = mongoose.connection; @@ -225,4 +237,5 @@ module.exports = { adminIPWhitelistSchema ), adminLogModel: mongoose.model("AdminLog", adminLogSchema), + taxiFareModel: mongoose.model("TaxiFare", taxiFareSchema), }; diff --git a/src/routes/fare.js b/src/routes/fare.js new file mode 100644 index 00000000..fbe7d2c5 --- /dev/null +++ b/src/routes/fare.js @@ -0,0 +1,28 @@ +const express = require("express"); +const { check } = require("express-validator"); +const router = express.Router(); + +const { validator } = require("../middlewares/validator"); +const { getTaxiFare, initDatabase } = require("../services/fare"); +const locations = require("../locations.json"); + +const checkTaxiFareParams = [ + check("start") + .isIn(Object.keys(locations)) //TODO: Change Location style of taxi-fare to match taxi-back + .withMessage("출발지가 올바르지 않습니다"), + check("goal") + .isIn(Object.keys(locations)) //TODO: Change Location style of taxi-fare to match taxi-back + .withMessage("도착지가 올바르지 않습니다"), + check("time") + .exists() + .withMessage("날짜/시간을 입력해주세요") + .isISO8601() + .withMessage("날짜/시간 형식이 올바르지 않습니다"), + validator, +]; + +router.get("/init", initDatabase); + +router.get("/:start-:goal/time/:time", getTaxiFare); + +module.exports = router; diff --git a/src/services/fare.js b/src/services/fare.js new file mode 100644 index 00000000..cdd65ba1 --- /dev/null +++ b/src/services/fare.js @@ -0,0 +1,138 @@ +const axios = require("axios"); + +const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); +const { taxiFareModel } = require("../modules/stores/mongo"); +const locations = require("../locations.json"); //TODO: Change Location style of taxi-fare to match taxi-back + +// Naver Cloud Platform Maps Directions 5 API Keys +const naverCloudApi = { + "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, + "X-NCP-APIGW-API-KEY": naverCloudApiKey, +}; + +// Initialize database +// Erase all previous data and sets all taxi fare to 0 +const initDatabase = async (req, res) => { + try { + // Remove all previous data + await taxiFareModel.deleteMany({}); + + //TODO: Change Location style of taxi-fare to match taxi-back + for (let skey in locations) { + //TODO: Change Location style of taxi-fare to match taxi-back + for (let gkey in locations) { + if (skey === gkey) continue; + let tableFare = []; + if ( + (skey === "카이스트 본원" && gkey === "대전역") || + (skey === "대전역" && gkey === "카이스트 본원") + ) { + for (let i = 0; i < 48; i++) { + tableFare.push({ + start: skey, + goal: gkey, + time: i, + fare: 0, + }); + } + } else { + tableFare.push({ + start: skey, + goal: gkey, + time: 0, + fare: 0, + }); + } + await taxiFareModel.insertMany(tableFare); + } + } + res.state(200).json({ message: "TaxiFare Database initialized" }); + } catch (err) { + res.status(500).json({ error: "Failed with exception " + err.message }); + } +}; + +/** + * 주어진 start, goal, time에 대한 택시 요금을 반환합니다. + * @param {Request} req - 파라미터로 start, goal, time을 받습니다. + * - @param {String} start - 출발지 + * - @param {String} goal - 도착지 + * - @param {Date} time - 출발 시간 (ISO 8601) + */ +const getTaxiFare = async (req, res) => { + try { + let start = locations[req.params.start]; //TODO: Change Location style of taxi-fare to match taxi-back + let goal = locations[req.params.goal]; //TODO: Change Location style of taxi-fare to match taxi-back + let time = new Date(req.params.time); + let sTime = time.getHours() * 2 + Math.floor(time.getMinutes() / 30); // Scaled Time. 0 ~ 47 (0:00 ~ 23:30) + + console.log(taxiFareModel); + let taxiFare = await taxiFareModel + .findOne( + { + start: req.params.start, + goal: req.params.goal, + time: sTime, + }, + function (err, docs) { + if (err) + console.log( + "Error occured while finding TaxiFare documents: " + err.message + ); + } + ) + .clone(); + + if ( + taxiFare && + new Date() - taxiFare.updatedAt < 24 * 60 * 60 * 1000 && + taxiFare.fare !== 0 + ) { + res.json({ fare: taxiFare.fare }); + } else { + let response = await axios.get( + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, + { headers: naverCloudApi } + ); + + let fare = response.data.route.traoptimal[0].summary.taxiFare; + if (!taxiFare) { + taxiFare = new taxiFareModel( + { + start: req.params.start, + goal: req.params.goal, + time: sTime, + fare: fare, + }, + function (err, docs) { + if (err) + console.log( + "Error occured while creating a new document of TaxiFare: " + + err.message + ); + } + ); // 만일 document가 중간에 삭제되어 공백이 생겼을 경우 채우는 용도 + } else { + await taxiFareModel + .updateOne( + { start: req.params.start, goal: req.params.goal, time: sTime }, + { fare: fare }, + function (err, docs) { + if (err) + console.log( + "Error occured while updating TaxiFare document: " + + err.message + ); + } + ) + .clone(); + } + res.json({ fare: fare }); + } + } catch (err) { + console.log(err); + res.status(500).json({ error: "Failed with exception: " + err.message }); + } +}; + +module.exports = { initDatabase, getTaxiFare }; From 1d7a1066f517e55a5582da034658bad5a8d396bb Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 6 Feb 2024 21:55:23 +0900 Subject: [PATCH 02/33] =?UTF-8?q?Add:=20Cron=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B8=B0=EC=9E=91=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/fare.js | 10 +++ src/schedules/index.js | 2 + src/schedules/updateMajorTaxiFare.js | 16 ++++ src/schedules/updateMinorTaxiFare.js | 23 +++++ src/services/fare.js | 128 ++++++++++++++++----------- 5 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 src/schedules/updateMajorTaxiFare.js create mode 100644 src/schedules/updateMinorTaxiFare.js diff --git a/src/modules/fare.js b/src/modules/fare.js index e69de29b..f77d62fc 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -0,0 +1,10 @@ +/* + * 시간을 받아서 30분 단위로 변환해서 반환합니다. + * 00:00 ~ 23:59 -> 0 ~ 47 + * @param {Date} time 변환할 시간 + */ +const scaledTime = (time) => { + return time.getHours() * 2 + (time.getMinutes() >= 30 ? 1 : 0); +}; + +module.exports = { scaledTime }; diff --git a/src/schedules/index.js b/src/schedules/index.js index 97818b92..fda57d53 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -3,6 +3,8 @@ const cron = require("node-cron"); const registerSchedules = (app) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); + cron.schedule("0,30 * * * * ", require("./updateMajorTaxiFare")(app)); + cron.schedule("0 18 * * *", require("./updateMinorTaxiFare")(app)); }; module.exports = registerSchedules; diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js new file mode 100644 index 00000000..a21f305f --- /dev/null +++ b/src/schedules/updateMajorTaxiFare.js @@ -0,0 +1,16 @@ +const { scaledTime } = require("../modules/fare"); +const { updateTaxiFare } = require("../services/fare"); + +/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 캐싱합니다. */ +module.exports = (app) => async () => { + try { + start = "카이스트 본원"; + goal = "대전역"; + time = new Date(); + sTime = scaledTime(time); + await updateTaxiFare(start, goal, sTime); + await updateTaxiFare(goal, start, sTime); + } catch (err) { + console.log(err); + } +}; diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js new file mode 100644 index 00000000..9e0d5dda --- /dev/null +++ b/src/schedules/updateMinorTaxiFare.js @@ -0,0 +1,23 @@ +const { updateTaxiFare } = require("../services/fare"); +const locations = require("../locations.json"); //TODO: Change Location style of taxi-fare to match taxi-back + +/* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 캐싱합니다. */ +module.exports = (app) => async () => { + try { + for (let locStart in locations) { + for (let locGoal in locations) { + if (locStart === locGoal) continue; + if ( + (locStart === "카이스트 본원" && locGoal === "대전역") || + (locStart === "대전역" && locGoal === "카이스트 본원") + ) + continue; + else { + await updateTaxiFare(locStart, locGoal, 0); //18:00시의 택시 요금이지만 db에는 0으로 저장됨 + } + } + } + } catch (err) { + console.log(err); + } +}; diff --git a/src/services/fare.js b/src/services/fare.js index cdd65ba1..1e189821 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -2,6 +2,7 @@ const axios = require("axios"); const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); const { taxiFareModel } = require("../modules/stores/mongo"); +const { scaledTime } = require("../modules/fare"); const locations = require("../locations.json"); //TODO: Change Location style of taxi-fare to match taxi-back // Naver Cloud Platform Maps Directions 5 API Keys @@ -10,8 +11,12 @@ const naverCloudApi = { "X-NCP-APIGW-API-KEY": naverCloudApiKey, }; -// Initialize database -// Erase all previous data and sets all taxi fare to 0 +/* Initialize database + * 1. Erase all previous data + * 2. Sets all taxi fare to 0 + * 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정합니다. + * 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 time과 fare를 0으로 설정합니다. + */ const initDatabase = async (req, res) => { try { // Remove all previous data @@ -54,6 +59,8 @@ const initDatabase = async (req, res) => { /** * 주어진 start, goal, time에 대한 택시 요금을 반환합니다. + * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 매일 18:00시의 택시 요금을 반환합니다. + * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. * @param {Request} req - 파라미터로 start, goal, time을 받습니다. * - @param {String} start - 출발지 * - @param {String} goal - 도착지 @@ -64,11 +71,13 @@ const getTaxiFare = async (req, res) => { let start = locations[req.params.start]; //TODO: Change Location style of taxi-fare to match taxi-back let goal = locations[req.params.goal]; //TODO: Change Location style of taxi-fare to match taxi-back let time = new Date(req.params.time); - let sTime = time.getHours() * 2 + Math.floor(time.getMinutes() / 30); // Scaled Time. 0 ~ 47 (0:00 ~ 23:30) + let sTime = scaledTime(time); // Scaled Time. 0 ~ 47 (0:00 ~ 23:30) - console.log(taxiFareModel); - let taxiFare = await taxiFareModel - .findOne( + if ( + (req.params.start === "카이스트 본원" && req.params.goal === "대전역") || + (req.params.start === "대전역" && req.params.goal === "카이스트 본원") + ) { + let taxiFare = await taxiFareModel.findOne( { start: req.params.start, goal: req.params.goal, @@ -80,54 +89,43 @@ const getTaxiFare = async (req, res) => { "Error occured while finding TaxiFare documents: " + err.message ); } - ) - .clone(); - - if ( - taxiFare && - new Date() - taxiFare.updatedAt < 24 * 60 * 60 * 1000 && - taxiFare.fare !== 0 - ) { - res.json({ fare: taxiFare.fare }); - } else { - let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, - { headers: naverCloudApi } ); - - let fare = response.data.route.traoptimal[0].summary.taxiFare; - if (!taxiFare) { - taxiFare = new taxiFareModel( - { - start: req.params.start, - goal: req.params.goal, - time: sTime, - fare: fare, - }, - function (err, docs) { - if (err) - console.log( - "Error occured while creating a new document of TaxiFare: " + - err.message - ); - } - ); // 만일 document가 중간에 삭제되어 공백이 생겼을 경우 채우는 용도 + if (taxiFare.fare === 0) { + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + let response = await axios.get( + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, + { headers: naverCloudApi } + ); + res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); } else { - await taxiFareModel - .updateOne( - { start: req.params.start, goal: req.params.goal, time: sTime }, - { fare: fare }, - function (err, docs) { - if (err) - console.log( - "Error occured while updating TaxiFare document: " + - err.message - ); - } - ) - .clone(); + res.json({ fare: taxiFare.fare }); + } + } + // 카이스트 본원 <-> 대전역이 아닌 경우 + else { + let taxiFare = await taxiFareModel.findOne( + { + start: req.params.start, + goal: req.params.goal, + time: 0, + }, + function (err, docs) { + if (err) + console.log( + "Error occured while finding TaxiFare documents: " + err.message + ); + } + ); + if (taxiFare.fare === 0) { + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + let response = await axios.get( + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, + { headers: naverCloudApi } + ); + res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); + } else { + res.json({ fare: taxiFare.fare }); } - res.json({ fare: fare }); } } catch (err) { console.log(err); @@ -135,4 +133,30 @@ const getTaxiFare = async (req, res) => { } }; -module.exports = { initDatabase, getTaxiFare }; +/** + * 주어진 start, goal, sTime에 대한 택시 요금을 업데이트합니다. + * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. + * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. + * @param {String} locStart - 출발지 string + * @param {String} locGoal - 도착지 string + * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 47 (0:00 ~ 23:59)) + */ +const updateTaxiFare = async (locStart, locGoal, sTime) => { + let response = await axios.get( + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${locations[locStart]}&goal=${locations[locGoal]}&options=traoptimal`, + { headers: naverCloudApi } + ); + let fare = response.data.route.traoptimal[0].summary.taxiFare; + await taxiFareModel.updateOne( + { start: locStart, goal: locGoal, time: sTime }, + { fare: fare }, + function (err, docs) { + if (err) + console.log( + "Error occured while updating TaxiFare document: " + err.message + ); + } + ); +}; + +module.exports = { initDatabase, getTaxiFare, updateTaxiFare }; From 4de79263a6d499b662f1aa2a438ea0e73914063b Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 6 Feb 2024 22:10:16 +0900 Subject: [PATCH 03/33] =?UTF-8?q?Docs:=20=EC=A3=BC=EC=84=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/fare.js | 2 +- src/services/fare.js | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index f77d62fc..9dc1fe43 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -1,4 +1,4 @@ -/* +/** * 시간을 받아서 30분 단위로 변환해서 반환합니다. * 00:00 ~ 23:59 -> 0 ~ 47 * @param {Date} time 변환할 시간 diff --git a/src/services/fare.js b/src/services/fare.js index 1e189821..8e7ed2cc 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -11,11 +11,11 @@ const naverCloudApi = { "X-NCP-APIGW-API-KEY": naverCloudApiKey, }; -/* Initialize database +/** Initialize database * 1. Erase all previous data * 2. Sets all taxi fare to 0 - * 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정합니다. - * 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 time과 fare를 0으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 time과 fare를 0으로 설정합니다. */ const initDatabase = async (req, res) => { try { @@ -28,6 +28,7 @@ const initDatabase = async (req, res) => { for (let gkey in locations) { if (skey === gkey) continue; let tableFare = []; + // 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정 if ( (skey === "카이스트 본원" && gkey === "대전역") || (skey === "대전역" && gkey === "카이스트 본원") @@ -40,7 +41,9 @@ const initDatabase = async (req, res) => { fare: 0, }); } - } else { + } + // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 1개씩만 collection 지정 설정 + else { tableFare.push({ start: skey, goal: gkey, From 754396c7a68d205bcadd7cd2aecf838abaa496a4 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 6 Feb 2024 22:29:07 +0900 Subject: [PATCH 04/33] =?UTF-8?q?Add:=20=EB=84=A4=EC=9D=B4=EB=B2=84=20api?= =?UTF-8?q?=EC=9A=A9=20.env=20=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.example b/.env.example index ea107da9..9e2688e9 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,8 @@ CORS_WHITELIST=[CORS 정책에서 허용하는 도메인의 목록(e.g. ["http:/ GOOGLE_APPLICATION_CREDENTIALS=[GOOGLE_APPLICATION_CREDENTIALS JSON] TEST_ACCOUNTS=[스팍스SSO로 로그인시 무조건 테스트로 로그인이 가능한 허용 아이디 목록] SLACK_REPORT_WEBHOOK_URL=[Slack 웹훅 URL들이 담긴 JSON] +NAVER_MAP_API_ID=[네이버 지도 API ID] +NAVER_MAP_API_KEY=[네이버 지도 API KEY] # optional environment variables for taxiSampleGenerator SAMPLE_NUM_OF_ROOMS=[방의 개수] From de8eefc2e05edb5be5e956425cce38bf0b9b6029 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 13 Feb 2024 17:35:17 +0900 Subject: [PATCH 05/33] Add: Added validator & use project locations --- src/locations.json | 19 ----- src/routes/fare.js | 46 +++++++----- src/schedules/updateMinorTaxiFare.js | 9 ++- src/services/fare.js | 106 ++++++++++++++++----------- 4 files changed, 96 insertions(+), 84 deletions(-) delete mode 100644 src/locations.json diff --git a/src/locations.json b/src/locations.json deleted file mode 100644 index 73e5cc21..00000000 --- a/src/locations.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "카이스트 본원": "127.357913,36.373724", - "카이스트 문지캠퍼스": "127.396729,36.393039", - "대전역": "127.432885,36.331373", - "서대전역": "127.404002,36.322479", - "대전복합터미널": "127.436362,36.351862", - "유성 고속버스터미널": "127.336115,36.359945", - "유성 시외버스터미널": "127.330246,36.356030", - "대전청사 고속버스터미널": "127.388202,36.361294", - "대전청사 시외버스터미널": "127.377567,36.361290", - "갤러리아 타임월드": "127.378191,36.352513", - "궁동 로데오거리": "127.349825,36.360342", - "만년중학교": "127.3757119,36.366771", - "신세계백화점": "127.382517,36.3779922", - "월평역": "127.368200,36.3580706", - "유성구청": "127.355899,36.362081", - "유성구 예비군 훈련장": "127.341971,36.393751" - } - \ No newline at end of file diff --git a/src/routes/fare.js b/src/routes/fare.js index fbe7d2c5..d03b560e 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -1,28 +1,36 @@ const express = require("express"); -const { check } = require("express-validator"); +const { query } = require("express-validator"); const router = express.Router(); -const { validator } = require("../middlewares/validator"); +const validator = require("../middlewares/validator"); const { getTaxiFare, initDatabase } = require("../services/fare"); -const locations = require("../locations.json"); - -const checkTaxiFareParams = [ - check("start") - .isIn(Object.keys(locations)) //TODO: Change Location style of taxi-fare to match taxi-back - .withMessage("출발지가 올바르지 않습니다"), - check("goal") - .isIn(Object.keys(locations)) //TODO: Change Location style of taxi-fare to match taxi-back - .withMessage("도착지가 올바르지 않습니다"), - check("time") - .exists() - .withMessage("날짜/시간을 입력해주세요") - .isISO8601() - .withMessage("날짜/시간 형식이 올바르지 않습니다"), - validator, -]; +const { locationModel } = require("../modules/stores/mongo"); router.get("/init", initDatabase); -router.get("/:start-:goal/time/:time", getTaxiFare); +router.get( + "/getTaxiFare", + async (req, res, next) => { + req.locations = ( + await locationModel.find({ isValid: { $ne: false } }, "koName") + ).map((location) => location.koName); + next(); + }, + query("start").custom((value, { req }) => { + if (!req.locations.includes(value)) { + throw new Error("Invalid start location"); + } + return true; + }), + query("goal").custom((value, { req }) => { + if (!req.locations.includes(value)) { + throw new Error("Invalid goal location"); + } + return true; + }), + query("time").isISO8601(), + validator, + getTaxiFare +); module.exports = router; diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js index 9e0d5dda..66b2b2e9 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,11 +1,14 @@ const { updateTaxiFare } = require("../services/fare"); -const locations = require("../locations.json"); //TODO: Change Location style of taxi-fare to match taxi-back +const { locationModel } = require("../modules/stores/mongo"); /* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 캐싱합니다. */ module.exports = (app) => async () => { try { - for (let locStart in locations) { - for (let locGoal in locations) { + const location = ( + await locationModel.find({ isValid: { $ne: false } }, "koName") + ).map((location) => location.koName); + for (let locStart in location) { + for (let locGoal in location) { if (locStart === locGoal) continue; if ( (locStart === "카이스트 본원" && locGoal === "대전역") || diff --git a/src/services/fare.js b/src/services/fare.js index 8e7ed2cc..1944c912 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -1,9 +1,8 @@ const axios = require("axios"); const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); -const { taxiFareModel } = require("../modules/stores/mongo"); +const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); const { scaledTime } = require("../modules/fare"); -const locations = require("../locations.json"); //TODO: Change Location style of taxi-fare to match taxi-back // Naver Cloud Platform Maps Directions 5 API Keys const naverCloudApi = { @@ -22,10 +21,12 @@ const initDatabase = async (req, res) => { // Remove all previous data await taxiFareModel.deleteMany({}); - //TODO: Change Location style of taxi-fare to match taxi-back - for (let skey in locations) { - //TODO: Change Location style of taxi-fare to match taxi-back - for (let gkey in locations) { + const location = ( + await locationModel.find({ isValid: { $ne: false } }, "koName") + ).map((location) => location.koName); + + for (let skey in location) { + for (let gkey in location) { if (skey === gkey) continue; let tableFare = []; // 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정 @@ -63,7 +64,7 @@ const initDatabase = async (req, res) => { /** * 주어진 start, goal, time에 대한 택시 요금을 반환합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 매일 18:00시의 택시 요금을 반환합니다. - * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. + * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 존재하지 않을 경우에는 직접 호출합니다. * @param {Request} req - 파라미터로 start, goal, time을 받습니다. * - @param {String} start - 출발지 * - @param {String} goal - 도착지 @@ -71,32 +72,41 @@ const initDatabase = async (req, res) => { */ const getTaxiFare = async (req, res) => { try { - let start = locations[req.params.start]; //TODO: Change Location style of taxi-fare to match taxi-back - let goal = locations[req.params.goal]; //TODO: Change Location style of taxi-fare to match taxi-back - let time = new Date(req.params.time); + let start = await locationModel.findOne({ + koName: { $eq: req.query.start }, + }); + let goal = await locationModel + .findOne({ koName: { $eq: req.query.goal } }) + .clone(); + let time = new Date(req.query.time); let sTime = scaledTime(time); // Scaled Time. 0 ~ 47 (0:00 ~ 23:30) + // 카이스트 본원 <-> 대전역 if ( - (req.params.start === "카이스트 본원" && req.params.goal === "대전역") || - (req.params.start === "대전역" && req.params.goal === "카이스트 본원") + (start.koName === "카이스트 본원" && goal.koName === "대전역") || + (start.koName === "대전역" && goal.koName === "카이스트 본원") ) { - let taxiFare = await taxiFareModel.findOne( - { - start: req.params.start, - goal: req.params.goal, - time: sTime, - }, - function (err, docs) { - if (err) - console.log( - "Error occured while finding TaxiFare documents: " + err.message - ); - } - ); + let taxiFare = await taxiFareModel + .findOne( + { + start: start.koName, + goal: goal.koName, + time: sTime, + }, + function (err, docs) { + if (err) + console.log( + "Error occured while finding TaxiFare documents: " + err.message + ); + } + ) + .clone(); + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (taxiFare.fare === 0) { - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ + start.longitude + "," + start.latitude + }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, { headers: naverCloudApi } ); res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); @@ -106,23 +116,27 @@ const getTaxiFare = async (req, res) => { } // 카이스트 본원 <-> 대전역이 아닌 경우 else { - let taxiFare = await taxiFareModel.findOne( - { - start: req.params.start, - goal: req.params.goal, - time: 0, - }, - function (err, docs) { - if (err) - console.log( - "Error occured while finding TaxiFare documents: " + err.message - ); - } - ); + let taxiFare = await taxiFareModel + .findOne( + { + start: start.koName, + goal: goal.koName, + time: 0, + }, + function (err, docs) { + if (err) + console.log( + "Error occured while finding TaxiFare documents: " + err.message + ); + } + ) + .clone(); + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (taxiFare.fare === 0) { - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${start}&goal=${goal}&options=traoptimal`, + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ + start.longitude + "," + start.latitude + }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, { headers: naverCloudApi } ); res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); @@ -145,8 +159,14 @@ const getTaxiFare = async (req, res) => { * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 47 (0:00 ~ 23:59)) */ const updateTaxiFare = async (locStart, locGoal, sTime) => { + const start = await locationModel.findOne({ koName: { $eq: locStart } }); + const goal = await locationModel + .findOne({ koName: { $eq: locGoal } }) + .clone(); let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${locations[locStart]}&goal=${locations[locGoal]}&options=traoptimal`, + `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ + start.longitude + "," + start.latitude + }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, { headers: naverCloudApi } ); let fare = response.data.route.traoptimal[0].summary.taxiFare; From 163abbf7bd553868e8d2fc5c362fde0358857c9e Mon Sep 17 00:00:00 2001 From: ybmin Date: Sat, 9 Mar 2024 17:20:28 +0900 Subject: [PATCH 06/33] =?UTF-8?q?Add:=20=EC=9D=BC=EC=A3=BC=EC=9D=BC=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=EC=BA=90=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/fare.js | 7 ++++-- src/modules/stores/mongo.js | 2 +- src/schedules/updateMajorTaxiFare.js | 2 +- src/schedules/updateMinorTaxiFare.js | 5 ++-- src/services/fare.js | 36 +++++++++++++++------------- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index 9dc1fe43..14b9d3f9 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -1,10 +1,13 @@ /** * 시간을 받아서 30분 단위로 변환해서 반환합니다. - * 00:00 ~ 23:59 -> 0 ~ 47 + * 요일 정보도 하나로 관리 + * @summary 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) * @param {Date} time 변환할 시간 */ const scaledTime = (time) => { - return time.getHours() * 2 + (time.getMinutes() >= 30 ? 1 : 0); + return ( + 48 * time.getDay() + time.getHours() * 2 + (time.getMinutes() >= 30 ? 1 : 0) + ); }; module.exports = { scaledTime }; diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 0000e74c..5f0d5f58 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -176,7 +176,7 @@ const taxiFareSchema = Schema( { start: { type: String, required: true }, // 출발지 goal: { type: String, required: true }, // 도착지 - time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리, 0 ~ 47 (0:00 ~ 23:30)) + time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) fare: { type: Number, default: false }, // 예상 택시 요금 }, { diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js index a21f305f..876220b7 100644 --- a/src/schedules/updateMajorTaxiFare.js +++ b/src/schedules/updateMajorTaxiFare.js @@ -1,7 +1,7 @@ const { scaledTime } = require("../modules/fare"); const { updateTaxiFare } = require("../services/fare"); -/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 캐싱합니다. */ +/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ module.exports = (app) => async () => { try { start = "카이스트 본원"; diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js index 66b2b2e9..4099eaa7 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,12 +1,13 @@ const { updateTaxiFare } = require("../services/fare"); const { locationModel } = require("../modules/stores/mongo"); -/* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 캐싱합니다. */ +/* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 1주일 단위로 캐싱합니다. */ module.exports = (app) => async () => { try { const location = ( await locationModel.find({ isValid: { $ne: false } }, "koName") ).map((location) => location.koName); + const date = new Date(); for (let locStart in location) { for (let locGoal in location) { if (locStart === locGoal) continue; @@ -16,7 +17,7 @@ module.exports = (app) => async () => { ) continue; else { - await updateTaxiFare(locStart, locGoal, 0); //18:00시의 택시 요금이지만 db에는 0으로 저장됨 + await updateTaxiFare(locStart, locGoal, 48 * date.getDay()); // 18:00시의 택시 요금이지만 db에는 48*(요일) + 0으로 저장됨 } } } diff --git a/src/services/fare.js b/src/services/fare.js index 1944c912..31d999d3 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -13,8 +13,8 @@ const naverCloudApi = { /** Initialize database * 1. Erase all previous data * 2. Sets all taxi fare to 0 - * @summary 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정합니다. - * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 time과 fare를 0으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 0으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 0으로 설정합니다. time은 0으로 설정합니다. */ const initDatabase = async (req, res) => { try { @@ -29,12 +29,12 @@ const initDatabase = async (req, res) => { for (let gkey in location) { if (skey === gkey) continue; let tableFare = []; - // 카이스트 본원 <-> 대전역의 경우 48개의 시간대에 대한 택시 요금을 0으로 설정 + // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 0으로 설정 if ( (skey === "카이스트 본원" && gkey === "대전역") || (skey === "대전역" && gkey === "카이스트 본원") ) { - for (let i = 0; i < 48; i++) { + for (let i = 0; i < 336; i++) { tableFare.push({ start: skey, goal: gkey, @@ -43,14 +43,16 @@ const initDatabase = async (req, res) => { }); } } - // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 1개씩만 collection 지정 설정 + // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { - tableFare.push({ - start: skey, - goal: gkey, - time: 0, - fare: 0, - }); + for (let i = 0; i < 7; i++) { + tableFare.push({ + start: skey, + goal: gkey, + time: i * 48, + fare: 0, + }); + } } await taxiFareModel.insertMany(tableFare); } @@ -63,8 +65,8 @@ const initDatabase = async (req, res) => { /** * 주어진 start, goal, time에 대한 택시 요금을 반환합니다. - * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 매일 18:00시의 택시 요금을 반환합니다. - * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 존재하지 않을 경우에는 직접 호출합니다. + * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 1주일 전 매일 18:00시의 택시 요금을 반환합니다. + * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 1주일 전 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 해당 데이터가 존재하지 않을 경우에는 직접 호출해 보여줍니다. * @param {Request} req - 파라미터로 start, goal, time을 받습니다. * - @param {String} start - 출발지 * - @param {String} goal - 도착지 @@ -102,7 +104,7 @@ const getTaxiFare = async (req, res) => { ) .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (taxiFare.fare === 0) { + if (!taxiFare || taxiFare.fare === 0) { let response = await axios.get( `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ start.longitude + "," + start.latitude @@ -132,7 +134,7 @@ const getTaxiFare = async (req, res) => { ) .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (taxiFare.fare === 0) { + if (!taxiFare || taxiFare.fare === 0) { let response = await axios.get( `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ start.longitude + "," + start.latitude @@ -151,12 +153,12 @@ const getTaxiFare = async (req, res) => { }; /** - * 주어진 start, goal, sTime에 대한 택시 요금을 업데이트합니다. + * 주어진 start, goal, sTime에 대한 단일 택시 요금을 업데이트합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. * @param {String} locStart - 출발지 string * @param {String} locGoal - 도착지 string - * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 47 (0:00 ~ 23:59)) + * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) */ const updateTaxiFare = async (locStart, locGoal, sTime) => { const start = await locationModel.findOne({ koName: { $eq: locStart } }); From a8cce549a89209da1972caef4a6b4b13ff16364e Mon Sep 17 00:00:00 2001 From: ybmin Date: Wed, 13 Mar 2024 15:16:53 +0900 Subject: [PATCH 07/33] Refactor: change fare location schema string to object id --- src/modules/stores/mongo.js | 5 +- src/routes/fare.js | 4 +- src/schedules/updateMajorTaxiFare.js | 8 +- src/schedules/updateMinorTaxiFare.js | 20 +-- src/services/fare.js | 183 +++++++++++++++++---------- 5 files changed, 124 insertions(+), 96 deletions(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 5f0d5f58..21f84d8f 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -174,8 +174,9 @@ const adminLogSchema = Schema({ const taxiFareSchema = Schema( { - start: { type: String, required: true }, // 출발지 - goal: { type: String, required: true }, // 도착지 + start: { type: Schema.Types.ObjectId, required: true }, // 출발지 + goal: { type: Schema.Types.ObjectId, required: true }, // 도착지 + isMajor: { type: Boolean, default: false }, // 카이스트 본원 <-> 대전역 경로 여부 time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) fare: { type: Number, default: false }, // 예상 택시 요금 }, diff --git a/src/routes/fare.js b/src/routes/fare.js index d03b560e..566d0979 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -12,8 +12,8 @@ router.get( "/getTaxiFare", async (req, res, next) => { req.locations = ( - await locationModel.find({ isValid: { $ne: false } }, "koName") - ).map((location) => location.koName); + await locationModel.find({ isValid: { $ne: false } }, "_id") + ).map((location) => location._id); next(); }, query("start").custom((value, { req }) => { diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js index 876220b7..abaff9d9 100644 --- a/src/schedules/updateMajorTaxiFare.js +++ b/src/schedules/updateMajorTaxiFare.js @@ -1,16 +1,14 @@ const { scaledTime } = require("../modules/fare"); +const logger = require("../modules/logger"); const { updateTaxiFare } = require("../services/fare"); /* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ module.exports = (app) => async () => { try { - start = "카이스트 본원"; - goal = "대전역"; time = new Date(); sTime = scaledTime(time); - await updateTaxiFare(start, goal, sTime); - await updateTaxiFare(goal, start, sTime); + await updateTaxiFare(sTime, true); } catch (err) { - console.log(err); + logger.error(err); } }; diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js index 4099eaa7..b5201712 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,27 +1,11 @@ const { updateTaxiFare } = require("../services/fare"); -const { locationModel } = require("../modules/stores/mongo"); /* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 1주일 단위로 캐싱합니다. */ module.exports = (app) => async () => { try { - const location = ( - await locationModel.find({ isValid: { $ne: false } }, "koName") - ).map((location) => location.koName); const date = new Date(); - for (let locStart in location) { - for (let locGoal in location) { - if (locStart === locGoal) continue; - if ( - (locStart === "카이스트 본원" && locGoal === "대전역") || - (locStart === "대전역" && locGoal === "카이스트 본원") - ) - continue; - else { - await updateTaxiFare(locStart, locGoal, 48 * date.getDay()); // 18:00시의 택시 요금이지만 db에는 48*(요일) + 0으로 저장됨 - } - } - } + await updateTaxiFare(48 * date.getDay(), false); // 18:00시의 택시 요금이지만 db에는 48*(요일) + 0으로 저장됨 } catch (err) { - console.log(err); + logger.error(err); } }; diff --git a/src/services/fare.js b/src/services/fare.js index 31d999d3..28043b27 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -3,60 +3,95 @@ const axios = require("axios"); const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); const { scaledTime } = require("../modules/fare"); +const logger = require("../modules/logger"); // Naver Cloud Platform Maps Directions 5 API Keys const naverCloudApi = { "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, "X-NCP-APIGW-API-KEY": naverCloudApiKey, }; +const naverCloudApiCall = + "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; /** Initialize database * 1. Erase all previous data * 2. Sets all taxi fare to 0 - * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 0으로 설정합니다. - * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 0으로 설정합니다. time은 0으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 init 시점의 비용으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 init 시점의 비용으로 설정합니다. time은 0으로 설정합니다. */ const initDatabase = async (req, res) => { try { // Remove all previous data await taxiFareModel.deleteMany({}); - const location = ( - await locationModel.find({ isValid: { $ne: false } }, "koName") - ).map((location) => location.koName); + const location = await locationModel + .find({ isValid: { $ne: false } }) + .toArray(); - for (let skey in location) { - for (let gkey in location) { - if (skey === gkey) continue; + location.map((start) => { + location.map(async (goal) => { + if (start._id === goal._id) return; let tableFare = []; - // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 0으로 설정 - if ( - (skey === "카이스트 본원" && gkey === "대전역") || - (skey === "대전역" && gkey === "카이스트 본원") + // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 + if (start.koName === "카이스트 본원" && goal.koName === "대전역") { + const fare = ( + await axios.get( + `${ + naverCloudApiCall + start.longitude + "," + start.latitude + }&goal=${ + goal.longitude + "," + goal.latitude + }&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + start: start._id, + goal: goal._id, + time: i, + fare: fare, + isMajor: true, + }); + }); + } else if ( + start.koName === "대전역" && + goal.koName === "카이스트 본원" ) { - for (let i = 0; i < 336; i++) { + const fare = ( + await axios.get( + `${ + naverCloudApiCall + start.longitude + "," + start.latitude + }&goal=${ + goal.longitude + "," + goal.latitude + }&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(48 * 7)].map((_, i) => { tableFare.push({ - start: skey, - goal: gkey, + start: start._id, + goal: goal._id, time: i, - fare: 0, + fare: fare, + isMajor: true, }); - } + }); } // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { - for (let i = 0; i < 7; i++) { + [...Array(7)].map((_, i) => { tableFare.push({ - start: skey, - goal: gkey, + start: start, + goal: goal, time: i * 48, fare: 0, + isMajor: false, }); - } + }); } await taxiFareModel.insertMany(tableFare); - } - } + }); + }); res.state(200).json({ message: "TaxiFare Database initialized" }); } catch (err) { res.status(500).json({ error: "Failed with exception " + err.message }); @@ -68,20 +103,20 @@ const initDatabase = async (req, res) => { * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 1주일 전 매일 18:00시의 택시 요금을 반환합니다. * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 1주일 전 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 해당 데이터가 존재하지 않을 경우에는 직접 호출해 보여줍니다. * @param {Request} req - 파라미터로 start, goal, time을 받습니다. - * - @param {String} start - 출발지 - * - @param {String} goal - 도착지 + * - @param {mongoose.Schema.Types.ObjectId} start - 출발지 + * - @param {mongoose.Schema.Types.ObjectId} goal - 도착지 * - @param {Date} time - 출발 시간 (ISO 8601) */ const getTaxiFare = async (req, res) => { try { - let start = await locationModel.findOne({ - koName: { $eq: req.query.start }, + const start = await locationModel.findOne({ + _id: { $eq: req.query.start }, }); - let goal = await locationModel - .findOne({ koName: { $eq: req.query.goal } }) + const goal = await locationModel + .findOne({ _id: { $eq: req.query.goal } }) .clone(); - let time = new Date(req.query.time); - let sTime = scaledTime(time); // Scaled Time. 0 ~ 47 (0:00 ~ 23:30) + const time = new Date(req.query.time); + const sTime = scaledTime(time); // Scaled Time // 카이스트 본원 <-> 대전역 if ( @@ -91,13 +126,14 @@ const getTaxiFare = async (req, res) => { let taxiFare = await taxiFareModel .findOne( { - start: start.koName, - goal: goal.koName, + start: start._id, + goal: goal._id, time: sTime, + isMajor: true, }, function (err, docs) { if (err) - console.log( + logger.error( "Error occured while finding TaxiFare documents: " + err.message ); } @@ -106,9 +142,9 @@ const getTaxiFare = async (req, res) => { //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ - start.longitude + "," + start.latitude - }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ + goal.longitude + "," + goal.latitude + }&options=traoptimal`, { headers: naverCloudApi } ); res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); @@ -121,13 +157,14 @@ const getTaxiFare = async (req, res) => { let taxiFare = await taxiFareModel .findOne( { - start: start.koName, - goal: goal.koName, + start: start._id, + goal: goal._id, time: 0, + isMajor: false, }, function (err, docs) { if (err) - console.log( + logger.error( "Error occured while finding TaxiFare documents: " + err.message ); } @@ -136,9 +173,9 @@ const getTaxiFare = async (req, res) => { //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ - start.longitude + "," + start.latitude - }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ + goal.longitude + "," + goal.latitude + }&options=traoptimal`, { headers: naverCloudApi } ); res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); @@ -147,7 +184,7 @@ const getTaxiFare = async (req, res) => { } } } catch (err) { - console.log(err); + logger.error("Failed with exception: " + err.message); res.status(500).json({ error: "Failed with exception: " + err.message }); } }; @@ -156,32 +193,40 @@ const getTaxiFare = async (req, res) => { * 주어진 start, goal, sTime에 대한 단일 택시 요금을 업데이트합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. - * @param {String} locStart - 출발지 string - * @param {String} locGoal - 도착지 string * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) + * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 여부 */ -const updateTaxiFare = async (locStart, locGoal, sTime) => { - const start = await locationModel.findOne({ koName: { $eq: locStart } }); - const goal = await locationModel - .findOne({ koName: { $eq: locGoal } }) - .clone(); - let response = await axios.get( - `https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=${ - start.longitude + "," + start.latitude - }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, - { headers: naverCloudApi } - ); - let fare = response.data.route.traoptimal[0].summary.taxiFare; - await taxiFareModel.updateOne( - { start: locStart, goal: locGoal, time: sTime }, - { fare: fare }, - function (err, docs) { - if (err) - console.log( - "Error occured while updating TaxiFare document: " + err.message - ); - } - ); +const updateTaxiFare = async (sTime, isMajor) => { + const prevFare = await taxiFareModel.findOne({ + time: sTime, + isMajor: isMajor, + }); + prevFare.map(async (item) => { + const start = await locationModel.findOne({ _id: item.start }); + const goal = await locationModel.findOne({ _id: item.goal }); + const fare = ( + await axios.get( + `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ + goal.longitude + "," + goal.latitude + }&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + await taxiFareModel.updateOne( + { start: item.start, goal: item.goal, time: sTime }, + { fare: fare }, + function (err, docs) { + if (err) + logger.error( + "Error occured while updating TaxiFare document: " + err.message + ); + } + ); + }); }; -module.exports = { initDatabase, getTaxiFare, updateTaxiFare }; +module.exports = { + initDatabase, + getTaxiFare, + updateTaxiFare, +}; From 1120f429183524a026ab510377a36c6f74d89934 Mon Sep 17 00:00:00 2001 From: ybmin Date: Wed, 13 Mar 2024 21:56:18 +0900 Subject: [PATCH 08/33] Refactor: express-validator to ajv & swagger docs --- loadenv.js | 2 +- src/routes/docs/fare.js | 78 +++++++++++++++++++++++++++ src/routes/docs/schemas/fareSchema.js | 24 +++++++++ src/routes/fare.js | 32 ++--------- src/services/fare.js | 55 +++++++++++-------- 5 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 src/routes/docs/fare.js create mode 100644 src/routes/docs/schemas/fareSchema.js diff --git a/loadenv.js b/loadenv.js index 411e10f9..9269f3e1 100644 --- a/loadenv.js +++ b/loadenv.js @@ -1,5 +1,5 @@ // 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옴 -require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` }); +require("dotenv").config({ path: `./.env.development` }); module.exports = { nodeEnv: process.env.NODE_ENV, // required ("production" or "development" or "test") diff --git a/src/routes/docs/fare.js b/src/routes/docs/fare.js new file mode 100644 index 00000000..59da6d97 --- /dev/null +++ b/src/routes/docs/fare.js @@ -0,0 +1,78 @@ +const { response } = require("express"); +const { objectIdPattern } = require("./utils"); + +const tag = "fare"; +const apiPrefix = "/fare"; + +const fareDocs = {}; +fareDocs[`${apiPrefix}/init`] = { + post: { + tags: [tag], + summary: "택시 요금 db 초기화", + }, + response: { + 200: { + description: "TaxiFare Database initialized", + content: { + "text/plain": { + schema: { + type: "string", + example: "TaxiFare Database initialized", + }, + }, + }, + }, + 500: { + description: "TaxiFare Database failed", + content: { + "text/html": { + example: "fare/init : TaxiFare Database failed", + }, + }, + }, + }, +}; + +fareDocs[`${apiPrefix}/getTaxiFare`] = { + get: { + tags: [tag], + summary: "예상 택시 요금 반환", + description: + "start, goal, time에 따라 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 1주일 전 매일 18:00시의 택시 요금을 반환합니다.
카이스트 본원 <-> 대전역의 경우, cron으로 1주일 전 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 해당 데이터가 존재하지 않을 경우에는 직접 호출해 보여줍니다.", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + start: { type: "string", pattern: objectIdPattern }, + goal: { type: "string", pattern: objectIdPattern }, + time: { type: "string", format: "date-time" }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "예상 택시 요금 반환 성공", + content: { + "text/plain": { + schema: { + type: "number", + example: 10000, + }, + }, + }, + }, + 500: { + description: "fare/getTaxiFare: Failed to load taxi fare", + content: { + "text/html": { + example: "fare/getTaxiFare: Failed to load taxi fare", + }, + }, + }, + }, + }, +}; diff --git a/src/routes/docs/schemas/fareSchema.js b/src/routes/docs/schemas/fareSchema.js new file mode 100644 index 00000000..ff887663 --- /dev/null +++ b/src/routes/docs/schemas/fareSchema.js @@ -0,0 +1,24 @@ +const { objectIdPattern } = require("../utils"); + +const fareSchema = { + getTaxiFare: { + type: "object", + required: ["start", "goal", "time"], + properties: { + start: { + type: "string", + format: objectIdPattern, + }, + goal: { + type: "string", + format: objectIdPattern, + }, + time: { + type: "string", + format: "date-time", + }, + }, + errorMessage: "validation: bad request", + }, +}; +module.exports = fareSchema; diff --git a/src/routes/fare.js b/src/routes/fare.js index 566d0979..7441d0ee 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -1,36 +1,12 @@ const express = require("express"); -const { query } = require("express-validator"); +const fareSchema = require("./docs/schemas/fareSchema"); +const { validateQuery } = require("../middlewares/ajv"); const router = express.Router(); -const validator = require("../middlewares/validator"); const { getTaxiFare, initDatabase } = require("../services/fare"); -const { locationModel } = require("../modules/stores/mongo"); -router.get("/init", initDatabase); +router.post("/init", initDatabase); -router.get( - "/getTaxiFare", - async (req, res, next) => { - req.locations = ( - await locationModel.find({ isValid: { $ne: false } }, "_id") - ).map((location) => location._id); - next(); - }, - query("start").custom((value, { req }) => { - if (!req.locations.includes(value)) { - throw new Error("Invalid start location"); - } - return true; - }), - query("goal").custom((value, { req }) => { - if (!req.locations.includes(value)) { - throw new Error("Invalid goal location"); - } - return true; - }), - query("time").isISO8601(), - validator, - getTaxiFare -); +router.get("/getTaxiFare", validateQuery(fareSchema.getTaxiFare), getTaxiFare); module.exports = router; diff --git a/src/services/fare.js b/src/services/fare.js index 28043b27..3526465b 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -92,9 +92,9 @@ const initDatabase = async (req, res) => { await taxiFareModel.insertMany(tableFare); }); }); - res.state(200).json({ message: "TaxiFare Database initialized" }); + res.status(200).send("TaxiFare Database initialized"); } catch (err) { - res.status(500).json({ error: "Failed with exception " + err.message }); + res.status(500).json({ error: "fare/init: TaxiFare Database failed" }); } }; @@ -115,15 +115,18 @@ const getTaxiFare = async (req, res) => { const goal = await locationModel .findOne({ _id: { $eq: req.query.goal } }) .clone(); - const time = new Date(req.query.time); - const sTime = scaledTime(time); // Scaled Time + const sTime = scaledTime(new Date(req.query.time)); + + if (!start || !goal) { + res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); + } // 카이스트 본원 <-> 대전역 if ( (start.koName === "카이스트 본원" && goal.koName === "대전역") || (start.koName === "대전역" && goal.koName === "카이스트 본원") ) { - let taxiFare = await taxiFareModel + const taxiFare = await taxiFareModel .findOne( { start: start._id, @@ -141,20 +144,22 @@ const getTaxiFare = async (req, res) => { .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { - let response = await axios.get( - `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ - goal.longitude + "," + goal.latitude - }&options=traoptimal`, - { headers: naverCloudApi } - ); - res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); + const fare = ( + await axios.get( + `${ + naverCloudApiCall + start.longitude + "," + start.latitude + }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + res.status(200).send(fare); } else { - res.json({ fare: taxiFare.fare }); + res.status(200).send(taxiFare.fare); } } // 카이스트 본원 <-> 대전역이 아닌 경우 else { - let taxiFare = await taxiFareModel + const taxiFare = await taxiFareModel .findOne( { start: start._id, @@ -172,20 +177,24 @@ const getTaxiFare = async (req, res) => { .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { - let response = await axios.get( - `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ - goal.longitude + "," + goal.latitude - }&options=traoptimal`, - { headers: naverCloudApi } - ); - res.json({ fare: response.data.route.traoptimal[0].summary.taxiFare }); + const fare = ( + await axios.get( + `${ + naverCloudApiCall + start.longitude + "," + start.latitude + }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + res.status(200).send(fare); } else { - res.json({ fare: taxiFare.fare }); + res.status(200).send(taxiFare.fare); } } } catch (err) { logger.error("Failed with exception: " + err.message); - res.status(500).json({ error: "Failed with exception: " + err.message }); + res + .status(500) + .json({ error: "fare/getTaxiFare: Failed to load taxi fare" }); } }; From 2ac458a943136dd444251f067bf65733cb75895d Mon Sep 17 00:00:00 2001 From: ybmin Date: Wed, 13 Mar 2024 22:50:01 +0900 Subject: [PATCH 09/33] Refactor: start, goal to from, to --- src/modules/stores/mongo.js | 4 +- src/routes/docs/fare.js | 4 +- src/routes/docs/schemas/fareSchema.js | 6 +- src/services/fare.js | 91 +++++++++++++-------------- 4 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 21f84d8f..2f9ccbbd 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -174,8 +174,8 @@ const adminLogSchema = Schema({ const taxiFareSchema = Schema( { - start: { type: Schema.Types.ObjectId, required: true }, // 출발지 - goal: { type: Schema.Types.ObjectId, required: true }, // 도착지 + from: { type: Schema.Types.ObjectId, required: true }, // 출발지 + to: { type: Schema.Types.ObjectId, required: true }, // 도착지 isMajor: { type: Boolean, default: false }, // 카이스트 본원 <-> 대전역 경로 여부 time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) fare: { type: Number, default: false }, // 예상 택시 요금 diff --git a/src/routes/docs/fare.js b/src/routes/docs/fare.js index 59da6d97..bda96260 100644 --- a/src/routes/docs/fare.js +++ b/src/routes/docs/fare.js @@ -45,8 +45,8 @@ fareDocs[`${apiPrefix}/getTaxiFare`] = { schema: { type: "object", properties: { - start: { type: "string", pattern: objectIdPattern }, - goal: { type: "string", pattern: objectIdPattern }, + from: { type: "string", pattern: objectIdPattern }, + to: { type: "string", pattern: objectIdPattern }, time: { type: "string", format: "date-time" }, }, }, diff --git a/src/routes/docs/schemas/fareSchema.js b/src/routes/docs/schemas/fareSchema.js index ff887663..b725c42a 100644 --- a/src/routes/docs/schemas/fareSchema.js +++ b/src/routes/docs/schemas/fareSchema.js @@ -3,13 +3,13 @@ const { objectIdPattern } = require("../utils"); const fareSchema = { getTaxiFare: { type: "object", - required: ["start", "goal", "time"], + required: ["from", "to", "time"], properties: { - start: { + from: { type: "string", format: objectIdPattern, }, - goal: { + to: { type: "string", format: objectIdPattern, }, diff --git a/src/services/fare.js b/src/services/fare.js index 3526465b..9db0d9ac 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -28,49 +28,42 @@ const initDatabase = async (req, res) => { .find({ isValid: { $ne: false } }) .toArray(); - location.map((start) => { - location.map(async (goal) => { - if (start._id === goal._id) return; + location.map((from) => { + location.map(async (to) => { + if (from._id === to._id) return; let tableFare = []; // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 - if (start.koName === "카이스트 본원" && goal.koName === "대전역") { + if (from.koName === "카이스트 본원" && to.koName === "대전역") { const fare = ( await axios.get( `${ - naverCloudApiCall + start.longitude + "," + start.latitude - }&goal=${ - goal.longitude + "," + goal.latitude - }&options=traoptimal`, + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; [...Array(48 * 7)].map((_, i) => { tableFare.push({ - start: start._id, - goal: goal._id, + from: from._id, + to: to._id, time: i, fare: fare, isMajor: true, }); }); - } else if ( - start.koName === "대전역" && - goal.koName === "카이스트 본원" - ) { + } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { const fare = ( await axios.get( `${ - naverCloudApiCall + start.longitude + "," + start.latitude - }&goal=${ - goal.longitude + "," + goal.latitude - }&options=traoptimal`, + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; [...Array(48 * 7)].map((_, i) => { tableFare.push({ - start: start._id, - goal: goal._id, + from: from._id, + to: to._id, time: i, fare: fare, isMajor: true, @@ -81,8 +74,8 @@ const initDatabase = async (req, res) => { else { [...Array(7)].map((_, i) => { tableFare.push({ - start: start, - goal: goal, + from: from, + to: to, time: i * 48, fare: 0, isMajor: false, @@ -99,38 +92,38 @@ const initDatabase = async (req, res) => { }; /** - * 주어진 start, goal, time에 대한 택시 요금을 반환합니다. + * 주어진 from, to, time에 대한 택시 요금을 반환합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 1주일 전 매일 18:00시의 택시 요금을 반환합니다. * @summary 카이스트 본원 <-> 대전역의 경우, cron으로 1주일 전 미리 캐싱해놓은 데이터를 기반으로 주어진 시간에 대한 택시 요금을 반환합니다. 만일, 해당 데이터가 존재하지 않을 경우에는 직접 호출해 보여줍니다. - * @param {Request} req - 파라미터로 start, goal, time을 받습니다. - * - @param {mongoose.Schema.Types.ObjectId} start - 출발지 - * - @param {mongoose.Schema.Types.ObjectId} goal - 도착지 + * @param {Request} req - 파라미터로 from, to, time을 받습니다. + * - @param {mongoose.Schema.Types.ObjectId} from - 출발지 + * - @param {mongoose.Schema.Types.ObjectId} to - 도착지 * - @param {Date} time - 출발 시간 (ISO 8601) */ const getTaxiFare = async (req, res) => { try { - const start = await locationModel.findOne({ - _id: { $eq: req.query.start }, + const from = await locationModel.findOne({ + _id: { $eq: req.query.from }, }); - const goal = await locationModel - .findOne({ _id: { $eq: req.query.goal } }) + const to = await locationModel + .findOne({ _id: { $eq: req.query.to } }) .clone(); const sTime = scaledTime(new Date(req.query.time)); - if (!start || !goal) { + if (!from || !to) { res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); } // 카이스트 본원 <-> 대전역 if ( - (start.koName === "카이스트 본원" && goal.koName === "대전역") || - (start.koName === "대전역" && goal.koName === "카이스트 본원") + (from.koName === "카이스트 본원" && to.koName === "대전역") || + (from.koName === "대전역" && to.koName === "카이스트 본원") ) { const taxiFare = await taxiFareModel .findOne( { - start: start._id, - goal: goal._id, + from: from._id, + to: to._id, time: sTime, isMajor: true, }, @@ -146,9 +139,9 @@ const getTaxiFare = async (req, res) => { if (!taxiFare || taxiFare.fare === 0) { const fare = ( await axios.get( - `${ - naverCloudApiCall + start.longitude + "," + start.latitude - }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; @@ -162,8 +155,8 @@ const getTaxiFare = async (req, res) => { const taxiFare = await taxiFareModel .findOne( { - start: start._id, - goal: goal._id, + from: from._id, + to: to._id, time: 0, isMajor: false, }, @@ -179,9 +172,9 @@ const getTaxiFare = async (req, res) => { if (!taxiFare || taxiFare.fare === 0) { const fare = ( await axios.get( - `${ - naverCloudApiCall + start.longitude + "," + start.latitude - }&goal=${goal.longitude + "," + goal.latitude}&options=traoptimal`, + `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; @@ -199,7 +192,7 @@ const getTaxiFare = async (req, res) => { }; /** - * 주어진 start, goal, sTime에 대한 단일 택시 요금을 업데이트합니다. + * 주어진 from, to, sTime에 대한 단일 택시 요금을 업데이트합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) @@ -211,18 +204,18 @@ const updateTaxiFare = async (sTime, isMajor) => { isMajor: isMajor, }); prevFare.map(async (item) => { - const start = await locationModel.findOne({ _id: item.start }); - const goal = await locationModel.findOne({ _id: item.goal }); + const from = await locationModel.findOne({ _id: item.from }); + const to = await locationModel.findOne({ _id: item.to }); const fare = ( await axios.get( - `${naverCloudApiCall + start.longitude + "," + start.latitude}&goal=${ - goal.longitude + "," + goal.latitude + `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude }&options=traoptimal`, { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; await taxiFareModel.updateOne( - { start: item.start, goal: item.goal, time: sTime }, + { from: item.from, to: item.to, time: sTime }, { fare: fare }, function (err, docs) { if (err) From 840b99efbe86dd0f4dfa027f785dc61f28c91261 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 19 Mar 2024 23:10:42 +0900 Subject: [PATCH 10/33] Fix: init error case --- src/services/fare.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/services/fare.js b/src/services/fare.js index 9db0d9ac..f7ed765f 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -24,9 +24,7 @@ const initDatabase = async (req, res) => { // Remove all previous data await taxiFareModel.deleteMany({}); - const location = await locationModel - .find({ isValid: { $ne: false } }) - .toArray(); + const location = await locationModel.find({ isValid: { $eq: true } }); location.map((from) => { location.map(async (to) => { @@ -72,12 +70,21 @@ const initDatabase = async (req, res) => { } // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { + const fare = ( + await axios.get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + setTimeout(() => {}, 100); [...Array(7)].map((_, i) => { tableFare.push({ from: from, to: to, time: i * 48, - fare: 0, + fare: fare, isMajor: false, }); }); @@ -111,7 +118,9 @@ const getTaxiFare = async (req, res) => { const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { + console.log("asds"); res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); + return; } // 카이스트 본원 <-> 대전역 @@ -119,13 +128,13 @@ const getTaxiFare = async (req, res) => { (from.koName === "카이스트 본원" && to.koName === "대전역") || (from.koName === "대전역" && to.koName === "카이스트 본원") ) { + console.log("asds"); const taxiFare = await taxiFareModel .findOne( { from: from._id, to: to._id, time: sTime, - isMajor: true, }, function (err, docs) { if (err) @@ -145,9 +154,9 @@ const getTaxiFare = async (req, res) => { { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; - res.status(200).send(fare); + res.state(200).send(fare); } else { - res.status(200).send(taxiFare.fare); + res.state(200).send(taxiFare.fare); } } // 카이스트 본원 <-> 대전역이 아닌 경우 @@ -158,7 +167,6 @@ const getTaxiFare = async (req, res) => { from: from._id, to: to._id, time: 0, - isMajor: false, }, function (err, docs) { if (err) @@ -168,6 +176,7 @@ const getTaxiFare = async (req, res) => { } ) .clone(); + console.log(taxiFare.fare); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { const fare = ( @@ -178,13 +187,13 @@ const getTaxiFare = async (req, res) => { { headers: naverCloudApi } ) ).data.route.traoptimal[0].summary.taxiFare; - res.status(200).send(fare); + res.send(fare); } else { - res.status(200).send(taxiFare.fare); + res.send(taxiFare.fare); } } } catch (err) { - logger.error("Failed with exception: " + err.message); + logger.error(err.message); res .status(500) .json({ error: "fare/getTaxiFare: Failed to load taxi fare" }); @@ -199,11 +208,11 @@ const getTaxiFare = async (req, res) => { * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 여부 */ const updateTaxiFare = async (sTime, isMajor) => { - const prevFare = await taxiFareModel.findOne({ + const prevFares = await taxiFareModel.find({ time: sTime, isMajor: isMajor, }); - prevFare.map(async (item) => { + prevFares.map(async (item) => { const from = await locationModel.findOne({ _id: item.from }); const to = await locationModel.findOne({ _id: item.to }); const fare = ( From a1bab8dab0b5cb25d7fc1d5324c814d9a51d2daf Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 21 Mar 2024 17:08:10 +0900 Subject: [PATCH 11/33] Refactor: zod migration --- src/routes/docs/schemas/fareSchema.js | 34 ++--- src/routes/fare.js | 9 +- src/services/fare.js | 197 +++++++++++++++++--------- 3 files changed, 146 insertions(+), 94 deletions(-) diff --git a/src/routes/docs/schemas/fareSchema.js b/src/routes/docs/schemas/fareSchema.js index b725c42a..27f63450 100644 --- a/src/routes/docs/schemas/fareSchema.js +++ b/src/routes/docs/schemas/fareSchema.js @@ -1,24 +1,14 @@ -const { objectIdPattern } = require("../utils"); +const { z } = require("zod"); +const { zodToSchemaObject } = require("../utils"); +const { objectId } = require("../../../modules/patterns"); -const fareSchema = { - getTaxiFare: { - type: "object", - required: ["from", "to", "time"], - properties: { - from: { - type: "string", - format: objectIdPattern, - }, - to: { - type: "string", - format: objectIdPattern, - }, - time: { - type: "string", - format: "date-time", - }, - }, - errorMessage: "validation: bad request", - }, +const fareZod = { + getTaxiFare: z.object({ + from: z.string().regex(objectId), + to: z.string().regex(objectId), + time: z.string().datetime(), + }), }; -module.exports = fareSchema; +const fareSchema = zodToSchemaObject(fareZod); + +module.exports = { fareSchema, fareZod }; diff --git a/src/routes/fare.js b/src/routes/fare.js index 7441d0ee..42a0c75c 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -1,12 +1,11 @@ const express = require("express"); -const fareSchema = require("./docs/schemas/fareSchema"); -const { validateQuery } = require("../middlewares/ajv"); -const router = express.Router(); - +const { validateQuery } = require("../middlewares/zod"); +const { fareZod } = require("./docs/schemas/fareSchema"); const { getTaxiFare, initDatabase } = require("../services/fare"); +const router = express.Router(); router.post("/init", initDatabase); -router.get("/getTaxiFare", validateQuery(fareSchema.getTaxiFare), getTaxiFare); +router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); module.exports = router; diff --git a/src/services/fare.js b/src/services/fare.js index f7ed765f..15c949fb 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -50,44 +50,77 @@ const initDatabase = async (req, res) => { }); }); } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - const fare = ( - await axios.get( + await axios + .get( `${ naverCloudApiCall + from.longitude + "," + from.latitude }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, { headers: naverCloudApi } ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(48 * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, + .then((res) => { + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: true, + }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: 0, + isMajor: true, + }); + }); }); - }); } // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { - const fare = ( - await axios.get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } + await new Promise(() => + setTimeout( + async () => + await axios + .get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverCloudApi } + ) + .then((res) => { + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: false, + }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: 0, + isMajor: false, + }); + }); + }), + 100 ) - ).data.route.traoptimal[0].summary.taxiFare; - setTimeout(() => {}, 100); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * 48, - fare: fare, - isMajor: false, - }); - }); + ); } await taxiFareModel.insertMany(tableFare); }); @@ -109,16 +142,17 @@ const initDatabase = async (req, res) => { */ const getTaxiFare = async (req, res) => { try { - const from = await locationModel.findOne({ - _id: { $eq: req.query.from }, - }); + const from = await locationModel + .findOne({ + _id: { $eq: req.query.from }, + }) + .clone(); const to = await locationModel .findOne({ _id: { $eq: req.query.to } }) .clone(); const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { - console.log("asds"); res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); return; } @@ -128,7 +162,6 @@ const getTaxiFare = async (req, res) => { (from.koName === "카이스트 본원" && to.koName === "대전역") || (from.koName === "대전역" && to.koName === "카이스트 본원") ) { - console.log("asds"); const taxiFare = await taxiFareModel .findOne( { @@ -146,17 +179,23 @@ const getTaxiFare = async (req, res) => { .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare === 0) { - const fare = ( - await axios.get( + await axios + .get( `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, { headers: naverCloudApi } ) - ).data.route.traoptimal[0].summary.taxiFare; - res.state(200).send(fare); + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); } else { - res.state(200).send(taxiFare.fare); + res.status(200).json({ fare: taxiFare.fare }); } } // 카이스트 본원 <-> 대전역이 아닌 경우 @@ -176,20 +215,25 @@ const getTaxiFare = async (req, res) => { } ) .clone(); - console.log(taxiFare.fare); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare === 0) { - const fare = ( - await axios.get( + if (taxiFare === undefined) { + await axios + .get( `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, { headers: naverCloudApi } ) - ).data.route.traoptimal[0].summary.taxiFare; - res.send(fare); + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); } else { - res.send(taxiFare.fare); + res.status(200).json({ fare: taxiFare.fare }); } } } catch (err) { @@ -208,30 +252,49 @@ const getTaxiFare = async (req, res) => { * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 여부 */ const updateTaxiFare = async (sTime, isMajor) => { - const prevFares = await taxiFareModel.find({ - time: sTime, - isMajor: isMajor, - }); + const prevFares = await taxiFareModel + .find({ + time: sTime, + isMajor: isMajor, + }) + .clone(); prevFares.map(async (item) => { - const from = await locationModel.findOne({ _id: item.from }); - const to = await locationModel.findOne({ _id: item.to }); - const fare = ( - await axios.get( - `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverCloudApi } + const from = await locationModel.findOne({ _id: item.from }).clone(); + const to = await locationModel.findOne({ _id: item.to }).clone(); + + await new Promise(() => + setTimeout( + async () => + await axios + .get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + .catch((err) => { + logger.error(err.message); + }) + .then(async (res) => { + await taxiFareModel + .updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: res.data.route.traoptimal[0].summary.taxiFare }, + function (err, docs) { + if (err) + logger.error( + "Error occured while updating TaxiFare document: " + + err.message + ); + } + ) + .clone(); + }) + .catch((err) => { + logger.error(err.message); + }), + 100 ) - ).data.route.traoptimal[0].summary.taxiFare; - await taxiFareModel.updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: fare }, - function (err, docs) { - if (err) - logger.error( - "Error occured while updating TaxiFare document: " + err.message - ); - } ); }); }; From 4c884423ec4a2a3a95da41140fd833ae91a1b22a Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 21 Mar 2024 17:18:16 +0900 Subject: [PATCH 12/33] Docs: change to naver api optional --- loadenv.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/loadenv.js b/loadenv.js index 9269f3e1..4d6e5a7c 100644 --- a/loadenv.js +++ b/loadenv.js @@ -55,7 +55,6 @@ module.exports = { endAt: "2024-03-19T00:00:00+09:00", }, }, // optional - // Naver Cloud Platform Maps Directions 5 API Keys - naverCloudApiId: process.env.NAVER_MAP_API_ID, //required - naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //required + naverCloudApiId: process.env.NAVER_MAP_API_ID, // optional + naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //optional }; From f11d01c428a8da91070cf8f71afb57b19bb49195 Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 21 Mar 2024 18:41:41 +0900 Subject: [PATCH 13/33] Add: module init code & exception case --- app.js | 3 + loadenv.js | 4 +- src/modules/fare.js | 142 +++++++++++++++++++++++++++++++++++++++- src/routes/docs/fare.js | 27 -------- src/routes/fare.js | 4 +- src/services/fare.js | 131 ++++-------------------------------- 6 files changed, 159 insertions(+), 152 deletions(-) diff --git a/app.js b/app.js index e66ddcf3..1ac7310e 100644 --- a/app.js +++ b/app.js @@ -86,3 +86,6 @@ app.set("io", startSocketServer(serverHttp)); // [Schedule] 스케줄러 시작 require("./src/schedules")(app); + +// [Module] 택시 예상 비용 db 초기화 +require("./src/modules/fare").initDatabase(); diff --git a/loadenv.js b/loadenv.js index 4d6e5a7c..4e43740a 100644 --- a/loadenv.js +++ b/loadenv.js @@ -55,6 +55,6 @@ module.exports = { endAt: "2024-03-19T00:00:00+09:00", }, }, // optional - naverCloudApiId: process.env.NAVER_MAP_API_ID, // optional - naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //optional + naverCloudApiId: process.env.NAVER_MAP_API_ID || "none", // optional + naverCloudApiKey: process.env.NAVER_MAP_API_KEY || "none", //optional }; diff --git a/src/modules/fare.js b/src/modules/fare.js index 14b9d3f9..9f8ea96a 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -1,3 +1,9 @@ +const logger = require("./logger"); +const axios = require("axios"); + +const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); +const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); +const { scaledTime } = require("../modules/fare"); /** * 시간을 받아서 30분 단위로 변환해서 반환합니다. * 요일 정보도 하나로 관리 @@ -10,4 +16,138 @@ const scaledTime = (time) => { ); }; -module.exports = { scaledTime }; +// Naver Cloud Platform Maps Directions 5 API Keys +const naverCloudApi = { + "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, + "X-NCP-APIGW-API-KEY": naverCloudApiKey, +}; +const naverCloudApiCall = + "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; + +/** Initialize database + * 1. Erase all previous data + * 2. Sets all taxi fare to 0 + * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 init 시점의 비용으로 설정합니다. + * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 init 시점의 비용으로 설정합니다. time은 0으로 설정합니다. + */ +const initDatabase = async () => { + try { + if ( + naverCloudApi["X-NCP-APIGW-API-KEY"] == "none" || + naverCloudApi["X-NCP-APIGW-API-KEY-ID"] == "none" + ) { + logger.log( + "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." + ); + return; + } + // Remove all previous data + await taxiFareModel.deleteMany({}); + + const location = await locationModel.find({ isValid: { $eq: true } }); + + location.map((from) => { + location.map(async (to) => { + if (from._id === to._id) return; + let tableFare = []; + // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 + if (from.koName === "카이스트 본원" && to.koName === "대전역") { + const fare = ( + await axios.get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, + }); + }); + } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { + await axios + .get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverCloudApi } + ) + .then((res) => { + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: true, + }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(48 * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: 0, + isMajor: true, + }); + }); + }); + } + // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 + else { + await new Promise(() => + setTimeout( + async () => + await axios + .get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverCloudApi } + ) + .then((res) => { + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: false, + }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: 0, + isMajor: false, + }); + }); + }), + 100 + ) + ); + } + await taxiFareModel.insertMany(tableFare); + }); + }); + } catch (err) { + logger.error("Error occured while initializing database: " + err.message); + } +}; + +module.exports = { scaledTime, initDatabase }; diff --git a/src/routes/docs/fare.js b/src/routes/docs/fare.js index bda96260..4f349bd4 100644 --- a/src/routes/docs/fare.js +++ b/src/routes/docs/fare.js @@ -5,33 +5,6 @@ const tag = "fare"; const apiPrefix = "/fare"; const fareDocs = {}; -fareDocs[`${apiPrefix}/init`] = { - post: { - tags: [tag], - summary: "택시 요금 db 초기화", - }, - response: { - 200: { - description: "TaxiFare Database initialized", - content: { - "text/plain": { - schema: { - type: "string", - example: "TaxiFare Database initialized", - }, - }, - }, - }, - 500: { - description: "TaxiFare Database failed", - content: { - "text/html": { - example: "fare/init : TaxiFare Database failed", - }, - }, - }, - }, -}; fareDocs[`${apiPrefix}/getTaxiFare`] = { get: { diff --git a/src/routes/fare.js b/src/routes/fare.js index 42a0c75c..2d66e34a 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -1,11 +1,9 @@ const express = require("express"); const { validateQuery } = require("../middlewares/zod"); const { fareZod } = require("./docs/schemas/fareSchema"); -const { getTaxiFare, initDatabase } = require("../services/fare"); +const { getTaxiFare } = require("../services/fare"); const router = express.Router(); -router.post("/init", initDatabase); - router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); module.exports = router; diff --git a/src/services/fare.js b/src/services/fare.js index 15c949fb..7712e5dc 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -13,124 +13,6 @@ const naverCloudApi = { const naverCloudApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; -/** Initialize database - * 1. Erase all previous data - * 2. Sets all taxi fare to 0 - * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 init 시점의 비용으로 설정합니다. - * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 init 시점의 비용으로 설정합니다. time은 0으로 설정합니다. - */ -const initDatabase = async (req, res) => { - try { - // Remove all previous data - await taxiFareModel.deleteMany({}); - - const location = await locationModel.find({ isValid: { $eq: true } }); - - location.map((from) => { - location.map(async (to) => { - if (from._id === to._id) return; - let tableFare = []; - // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 - if (from.koName === "카이스트 본원" && to.koName === "대전역") { - const fare = ( - await axios.get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(48 * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, - }); - }); - } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - await axios - .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } - ) - .then((res) => { - [...Array(48 * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: true, - }); - }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(48 * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: 0, - isMajor: true, - }); - }); - }); - } - // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 - else { - await new Promise(() => - setTimeout( - async () => - await axios - .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverCloudApi } - ) - .then((res) => { - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * 48, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: false, - }); - }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * 48, - fare: 0, - isMajor: false, - }); - }); - }), - 100 - ) - ); - } - await taxiFareModel.insertMany(tableFare); - }); - }); - res.status(200).send("TaxiFare Database initialized"); - } catch (err) { - res.status(500).json({ error: "fare/init: TaxiFare Database failed" }); - } -}; - /** * 주어진 from, to, time에 대한 택시 요금을 반환합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, 1주일 전 매일 18:00시의 택시 요금을 반환합니다. @@ -142,6 +24,18 @@ const initDatabase = async (req, res) => { */ const getTaxiFare = async (req, res) => { try { + if ( + naverCloudApi["X-NCP-APIGW-API-KEY"] == "none" || + naverCloudApi["X-NCP-APIGW-API-KEY-ID"] == "none" + ) { + logger.log( + "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." + ); + res + .status(503) + .json({ error: "fare/getTaxiFare: Naver Cloud API not found" }); + return; + } const from = await locationModel .findOne({ _id: { $eq: req.query.from }, @@ -300,7 +194,6 @@ const updateTaxiFare = async (sTime, isMajor) => { }; module.exports = { - initDatabase, getTaxiFare, updateTaxiFare, }; From c7603babdf741b8efd863f79ae8fa234761d6751 Mon Sep 17 00:00:00 2001 From: ybmin Date: Mon, 25 Mar 2024 20:42:37 +0900 Subject: [PATCH 14/33] Fix: remove import --- src/modules/fare.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index 9f8ea96a..3e370cc7 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -3,7 +3,6 @@ const axios = require("axios"); const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); -const { scaledTime } = require("../modules/fare"); /** * 시간을 받아서 30분 단위로 변환해서 반환합니다. * 요일 정보도 하나로 관리 From 671a07ececdb99d506f499cf5073fa98980709b6 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 26 Mar 2024 20:22:13 +0900 Subject: [PATCH 15/33] Fix: naver api axios 429 error --- src/modules/fare.js | 92 +++++++++++++++++++++----------------------- src/services/fare.js | 69 ++++++++++++++++----------------- 2 files changed, 77 insertions(+), 84 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index 3e370cc7..d8df4091 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -3,6 +3,15 @@ const axios = require("axios"); const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); + +// Naver Cloud Platform Maps Directions 5 API Keys +const naverCloudApi = { + "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, + "X-NCP-APIGW-API-KEY": naverCloudApiKey, +}; +const naverCloudApiCall = + "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; + /** * 시간을 받아서 30분 단위로 변환해서 반환합니다. * 요일 정보도 하나로 관리 @@ -15,14 +24,6 @@ const scaledTime = (time) => { ); }; -// Naver Cloud Platform Maps Directions 5 API Keys -const naverCloudApi = { - "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, - "X-NCP-APIGW-API-KEY": naverCloudApiKey, -}; -const naverCloudApiCall = - "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; - /** Initialize database * 1. Erase all previous data * 2. Sets all taxi fare to 0 @@ -45,8 +46,9 @@ const initDatabase = async () => { const location = await locationModel.find({ isValid: { $eq: true } }); - location.map((from) => { - location.map(async (to) => { + location.map(async (from) => { + await location.reduce(async (acc, to) => { + await acc.then(); if (from._id === to._id) return; let tableFare = []; // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 @@ -102,47 +104,41 @@ const initDatabase = async () => { } // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { - await new Promise(() => - setTimeout( - async () => - await axios - .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverCloudApi } - ) - .then((res) => { - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * 48, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: false, - }); - }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * 48, - fare: 0, - isMajor: false, - }); - }); - }), - 100 + await axios + .get( + `${ + naverCloudApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverCloudApi } ) - ); + .then((res) => { + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: false, + }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * 48, + fare: 0, + isMajor: false, + }); + }); + }); } await taxiFareModel.insertMany(tableFare); - }); + await new Promise((resolve) => setTimeout(resolve, 200)); + return acc; + }, Promise.resolve()); }); } catch (err) { logger.error("Error occured while initializing database: " + err.message); diff --git a/src/services/fare.js b/src/services/fare.js index 7712e5dc..dcaaf17b 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -110,7 +110,7 @@ const getTaxiFare = async (req, res) => { ) .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (taxiFare === undefined) { + if (!taxiFare || taxiFare.fare === 0) { await axios .get( `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ @@ -152,45 +152,42 @@ const updateTaxiFare = async (sTime, isMajor) => { isMajor: isMajor, }) .clone(); - prevFares.map(async (item) => { + await prevFares.reduce(async (acc, item) => { const from = await locationModel.findOne({ _id: item.from }).clone(); const to = await locationModel.findOne({ _id: item.to }).clone(); - await new Promise(() => - setTimeout( - async () => - await axios - .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } - ) - .catch((err) => { - logger.error(err.message); - }) - .then(async (res) => { - await taxiFareModel - .updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: res.data.route.traoptimal[0].summary.taxiFare }, - function (err, docs) { - if (err) - logger.error( - "Error occured while updating TaxiFare document: " + - err.message - ); - } - ) - .clone(); - }) - .catch((err) => { - logger.error(err.message); - }), - 100 + await acc.then(); + await axios + .get( + `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverCloudApi } ) - ); - }); + .catch((err) => { + logger.error(err.message); + }) + .then(async (res) => { + await taxiFareModel + .updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: res.data.route.traoptimal[0].summary.taxiFare }, + function (err, docs) { + if (err) + logger.error( + "Error occured while updating TaxiFare document: " + + err.message + ); + } + ) + .clone(); + }) + .catch((err) => { + logger.error(err.message); + }); + await new Promise((resolve) => setTimeout(() => resolve, 200)); + return acc; + }, Promise.resolve()); }; module.exports = { From 17ac485bef03e074f784f4c8d8cef68d182b7755 Mon Sep 17 00:00:00 2001 From: ybmin Date: Wed, 27 Mar 2024 00:23:42 +0900 Subject: [PATCH 16/33] Fix: env file --- loadenv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index b4762a2f..7fc2508d 100644 --- a/loadenv.js +++ b/loadenv.js @@ -1,5 +1,5 @@ // 환경 변수에 따라 .env.production 또는 .env.development 파일을 읽어옴 -require("dotenv").config({ path: `./.env.development` }); +require("dotenv").config({ path: `./.env.${process.env.NODE_ENV}` }); module.exports = { nodeEnv: process.env.NODE_ENV, // required ("production" or "development" or "test") From 836e49eb99edaafe2cd2cc65b0bbdd8eb8d8ff8e Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 23 Apr 2024 22:11:41 +0900 Subject: [PATCH 17/33] Fix: naver api key none to null --- loadenv.js | 4 ++-- src/modules/fare.js | 4 ++-- src/services/fare.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/loadenv.js b/loadenv.js index 7fc2508d..0419e457 100644 --- a/loadenv.js +++ b/loadenv.js @@ -44,6 +44,6 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional - naverCloudApiId: process.env.NAVER_MAP_API_ID || "none", // optional - naverCloudApiKey: process.env.NAVER_MAP_API_KEY || "none", //optional + naverCloudApiId: process.env.NAVER_MAP_API_ID, // optional + naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //optional }; diff --git a/src/modules/fare.js b/src/modules/fare.js index d8df4091..72587962 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -33,8 +33,8 @@ const scaledTime = (time) => { const initDatabase = async () => { try { if ( - naverCloudApi["X-NCP-APIGW-API-KEY"] == "none" || - naverCloudApi["X-NCP-APIGW-API-KEY-ID"] == "none" + naverCloudApi["X-NCP-APIGW-API-KEY"] === null || + naverCloudApi["X-NCP-APIGW-API-KEY-ID"] === null ) { logger.log( "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." diff --git a/src/services/fare.js b/src/services/fare.js index dcaaf17b..99463386 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -25,8 +25,8 @@ const naverCloudApiCall = const getTaxiFare = async (req, res) => { try { if ( - naverCloudApi["X-NCP-APIGW-API-KEY"] == "none" || - naverCloudApi["X-NCP-APIGW-API-KEY-ID"] == "none" + naverCloudApi["X-NCP-APIGW-API-KEY"] === null || + naverCloudApi["X-NCP-APIGW-API-KEY-ID"] === null ) { logger.log( "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." From b63c72091dfd5923e7912dc156e26c6885f34a13 Mon Sep 17 00:00:00 2001 From: ybmin Date: Wed, 1 May 2024 22:39:45 +0900 Subject: [PATCH 18/33] Refactor: review contents --- app.js | 2 +- loadenv.js | 4 +-- src/modules/fare.js | 57 +++++++++++++++++++++------------------- src/services/fare.js | 62 +++++++++++++++++++++++++------------------- 4 files changed, 69 insertions(+), 56 deletions(-) diff --git a/app.js b/app.js index 37ae32c1..ef4fc765 100644 --- a/app.js +++ b/app.js @@ -88,4 +88,4 @@ app.set("io", startSocketServer(serverHttp)); require("./src/schedules")(app); // [Module] 택시 예상 비용 db 초기화 -require("./src/modules/fare").initDatabase(); +// require("./src/modules/fare").initDatabase(); diff --git a/loadenv.js b/loadenv.js index 0419e457..930c2982 100644 --- a/loadenv.js +++ b/loadenv.js @@ -44,6 +44,6 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional - naverCloudApiId: process.env.NAVER_MAP_API_ID, // optional - naverCloudApiKey: process.env.NAVER_MAP_API_KEY, //optional + naverMapApiId: process.env.NAVER_MAP_API_ID, // optional + naverMapApiKey: process.env.NAVER_MAP_API_KEY, //optional }; diff --git a/src/modules/fare.js b/src/modules/fare.js index 72587962..d5719c5a 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -1,17 +1,20 @@ const logger = require("./logger"); const axios = require("axios"); -const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); +const { naverMapApiId, naverMapApiKey } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); // Naver Cloud Platform Maps Directions 5 API Keys -const naverCloudApi = { - "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, - "X-NCP-APIGW-API-KEY": naverCloudApiKey, +const naverMapApi = { + "X-NCP-APIGW-API-KEY-ID": naverMapApiId, + "X-NCP-APIGW-API-KEY": naverMapApiKey, }; -const naverCloudApiCall = +const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; +// scaledTime에 사용하는 상수입니다. 0 ~ 47 (0:00 ~ 23:30) +const timeConstants = 48; + /** * 시간을 받아서 30분 단위로 변환해서 반환합니다. * 요일 정보도 하나로 관리 @@ -20,7 +23,9 @@ const naverCloudApiCall = */ const scaledTime = (time) => { return ( - 48 * time.getDay() + time.getHours() * 2 + (time.getMinutes() >= 30 ? 1 : 0) + timeConstants * time.getDay() + + time.getHours() * 2 + + (time.getMinutes() >= 30 ? 1 : 0) ); }; @@ -33,8 +38,8 @@ const scaledTime = (time) => { const initDatabase = async () => { try { if ( - naverCloudApi["X-NCP-APIGW-API-KEY"] === null || - naverCloudApi["X-NCP-APIGW-API-KEY-ID"] === null + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.log( "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." @@ -55,13 +60,13 @@ const initDatabase = async () => { if (from.koName === "카이스트 본원" && to.koName === "대전역") { const fare = ( await axios.get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } ) ).data.route.traoptimal[0].summary.taxiFare; - [...Array(48 * 7)].map((_, i) => { + [...Array(timeConstants * 7)].map((_, i) => { tableFare.push({ from: from._id, to: to._id, @@ -73,13 +78,13 @@ const initDatabase = async () => { } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { await axios .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } ) .then((res) => { - [...Array(48 * 7)].map((_, i) => { + [...Array(timeConstants * 7)].map((_, i) => { tableFare.push({ from: from._id, to: to._id, @@ -91,7 +96,7 @@ const initDatabase = async () => { }) .catch((err) => { logger.error(err.message); - [...Array(48 * 7)].map((_, i) => { + [...Array(timeConstants * 7)].map((_, i) => { tableFare.push({ from: from._id, to: to._id, @@ -106,17 +111,17 @@ const initDatabase = async () => { else { await axios .get( - `${ - naverCloudApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverCloudApi } + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } ) .then((res) => { [...Array(7)].map((_, i) => { tableFare.push({ from: from, to: to, - time: i * 48, + time: i * timeConstants, fare: res.data.route.traoptimal[0].summary.taxiFare, isMajor: false, }); @@ -128,7 +133,7 @@ const initDatabase = async () => { tableFare.push({ from: from, to: to, - time: i * 48, + time: i * timeConstants, fare: 0, isMajor: false, }); @@ -138,7 +143,7 @@ const initDatabase = async () => { await taxiFareModel.insertMany(tableFare); await new Promise((resolve) => setTimeout(resolve, 200)); return acc; - }, Promise.resolve()); + }); }); } catch (err) { logger.error("Error occured while initializing database: " + err.message); diff --git a/src/services/fare.js b/src/services/fare.js index 99463386..1e0073f3 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -1,16 +1,16 @@ const axios = require("axios"); -const { naverCloudApiId, naverCloudApiKey } = require("../../loadenv"); +const { naverMapApiId, naverMapApiKey } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); const { scaledTime } = require("../modules/fare"); const logger = require("../modules/logger"); // Naver Cloud Platform Maps Directions 5 API Keys -const naverCloudApi = { - "X-NCP-APIGW-API-KEY-ID": naverCloudApiId, - "X-NCP-APIGW-API-KEY": naverCloudApiKey, +const naverMapApi = { + "X-NCP-APIGW-API-KEY-ID": naverMapApiId, + "X-NCP-APIGW-API-KEY": naverMapApiKey, }; -const naverCloudApiCall = +const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; /** @@ -25,11 +25,11 @@ const naverCloudApiCall = const getTaxiFare = async (req, res) => { try { if ( - naverCloudApi["X-NCP-APIGW-API-KEY"] === null || - naverCloudApi["X-NCP-APIGW-API-KEY-ID"] === null + !naverMapApi["X-NCP-APIGW-API-KEY"] || + !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.log( - "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." + "There is no credential for Naver Map. Taxi Fare functions are disabled." ); res .status(503) @@ -50,12 +50,22 @@ const getTaxiFare = async (req, res) => { res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); return; } - - // 카이스트 본원 <-> 대전역 - if ( - (from.koName === "카이스트 본원" && to.koName === "대전역") || - (from.koName === "대전역" && to.koName === "카이스트 본원") - ) { + const isMajor = ( + await taxiFareModel + .findOne( + { from: from._id, to: to._id, time: 0 }, + { isMajor: true }, + (err, docs) => { + if (err) + logger.error( + "Error occured while finding TaxiFare documents: " + err.message + ); + } + ) + .clone() + ).isMajor; + // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) + if (isMajor) { const taxiFare = await taxiFareModel .findOne( { @@ -63,7 +73,7 @@ const getTaxiFare = async (req, res) => { to: to._id, time: sTime, }, - function (err, docs) { + (err, docs) => { if (err) logger.error( "Error occured while finding TaxiFare documents: " + err.message @@ -75,10 +85,10 @@ const getTaxiFare = async (req, res) => { if (!taxiFare || taxiFare.fare === 0) { await axios .get( - `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, - { headers: naverCloudApi } + { headers: naverMapApi } ) .then((text) => { res @@ -91,9 +101,7 @@ const getTaxiFare = async (req, res) => { } else { res.status(200).json({ fare: taxiFare.fare }); } - } - // 카이스트 본원 <-> 대전역이 아닌 경우 - else { + } else { const taxiFare = await taxiFareModel .findOne( { @@ -101,7 +109,7 @@ const getTaxiFare = async (req, res) => { to: to._id, time: 0, }, - function (err, docs) { + (err, docs) => { if (err) logger.error( "Error occured while finding TaxiFare documents: " + err.message @@ -113,10 +121,10 @@ const getTaxiFare = async (req, res) => { if (!taxiFare || taxiFare.fare === 0) { await axios .get( - `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, - { headers: naverCloudApi } + { headers: naverMapApi } ) .then((text) => { res @@ -159,10 +167,10 @@ const updateTaxiFare = async (sTime, isMajor) => { await acc.then(); await axios .get( - `${naverCloudApiCall + from.longitude + "," + from.latitude}&goal=${ + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, - { headers: naverCloudApi } + { headers: naverMapApi } ) .catch((err) => { logger.error(err.message); @@ -172,7 +180,7 @@ const updateTaxiFare = async (sTime, isMajor) => { .updateOne( { from: item.from, to: item.to, time: sTime }, { fare: res.data.route.traoptimal[0].summary.taxiFare }, - function (err, docs) { + (err, docs) => { if (err) logger.error( "Error occured while updating TaxiFare document: " + @@ -187,7 +195,7 @@ const updateTaxiFare = async (sTime, isMajor) => { }); await new Promise((resolve) => setTimeout(() => resolve, 200)); return acc; - }, Promise.resolve()); + }); }; module.exports = { From b421a3857c4f97f2a848a1a4013b2444fa8e87a3 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 7 May 2024 19:52:53 +0900 Subject: [PATCH 19/33] Fix: enable commented code --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index ef4fc765..37ae32c1 100644 --- a/app.js +++ b/app.js @@ -88,4 +88,4 @@ app.set("io", startSocketServer(serverHttp)); require("./src/schedules")(app); // [Module] 택시 예상 비용 db 초기화 -// require("./src/modules/fare").initDatabase(); +require("./src/modules/fare").initDatabase(); From 3ea9766f4c8a220e46bb01fbf352e0d44597974e Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 7 May 2024 20:35:38 +0900 Subject: [PATCH 20/33] Fix: undo promise resolve --- src/modules/fare.js | 44 ++++++++++++++++---------------------------- src/services/fare.js | 4 ++-- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index d5719c5a..e3f4b02f 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -42,7 +42,7 @@ const initDatabase = async () => { !naverMapApi["X-NCP-APIGW-API-KEY-ID"] ) { logger.log( - "Naver Cloud API가 존재하지 않습니다.택시 비용 관련 기능을 사용할 수 없습니다." + "There is no credential for Naver Map. Taxi Fare functions are disabled." ); return; } @@ -52,8 +52,9 @@ const initDatabase = async () => { const location = await locationModel.find({ isValid: { $eq: true } }); location.map(async (from) => { - await location.reduce(async (acc, to) => { - await acc.then(); + location.reduce(async (acc, to) => { + logger.info(`Initializing fare from ${from.koName} to ${to.koName}`); + await acc; if (from._id === to._id) return; let tableFare = []; // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 @@ -76,36 +77,23 @@ const initDatabase = async () => { }); }); } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - await axios - .get( + const fare = ( + await axios.get( `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ to.longitude + "," + to.latitude }&options=traoptimal`, { headers: naverMapApi } ) - .then((res) => { - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: true, - }); - }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: 0, - isMajor: true, - }); - }); + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, }); + }); } // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 else { @@ -143,7 +131,7 @@ const initDatabase = async () => { await taxiFareModel.insertMany(tableFare); await new Promise((resolve) => setTimeout(resolve, 200)); return acc; - }); + }, Promise.resolve()); }); } catch (err) { logger.error("Error occured while initializing database: " + err.message); diff --git a/src/services/fare.js b/src/services/fare.js index 1e0073f3..f922ad36 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -164,7 +164,7 @@ const updateTaxiFare = async (sTime, isMajor) => { const from = await locationModel.findOne({ _id: item.from }).clone(); const to = await locationModel.findOne({ _id: item.to }).clone(); - await acc.then(); + await acc; await axios .get( `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ @@ -195,7 +195,7 @@ const updateTaxiFare = async (sTime, isMajor) => { }); await new Promise((resolve) => setTimeout(() => resolve, 200)); return acc; - }); + }, Promise.resolve()); }; module.exports = { From b72bfcc2b2f7fe565c749a8bae8181884b0803e5 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 7 May 2024 20:37:49 +0900 Subject: [PATCH 21/33] Docs: comment added --- src/modules/fare.js | 2 +- src/services/fare.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index e3f4b02f..8ea2e239 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -131,7 +131,7 @@ const initDatabase = async () => { await taxiFareModel.insertMany(tableFare); await new Promise((resolve) => setTimeout(resolve, 200)); return acc; - }, Promise.resolve()); + }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 }); } catch (err) { logger.error("Error occured while initializing database: " + err.message); diff --git a/src/services/fare.js b/src/services/fare.js index f922ad36..d0cf0ada 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -195,7 +195,7 @@ const updateTaxiFare = async (sTime, isMajor) => { }); await new Promise((resolve) => setTimeout(() => resolve, 200)); return acc; - }, Promise.resolve()); + }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 }; module.exports = { From cf1a7c8168acf369e446c8b2f072a451cef010d2 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 7 May 2024 20:41:33 +0900 Subject: [PATCH 22/33] Fix: unusual case --- src/services/fare.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/fare.js b/src/services/fare.js index d0cf0ada..534db849 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -82,7 +82,7 @@ const getTaxiFare = async (req, res) => { ) .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare === 0) { + if (!taxiFare || taxiFare.fare <= 0) { await axios .get( `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ @@ -118,7 +118,7 @@ const getTaxiFare = async (req, res) => { ) .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare === 0) { + if (!taxiFare || taxiFare.fare <= 0) { await axios .get( `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ From 5b8ee062fd0d79d249876358ff4594d7bf515c82 Mon Sep 17 00:00:00 2001 From: chlehdwon Date: Tue, 14 May 2024 23:24:56 +0900 Subject: [PATCH 23/33] Docs: add fare docs to swagger --- src/routes/docs/fare.js | 11 +++++++---- src/routes/docs/swaggerDocs.js | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/routes/docs/fare.js b/src/routes/docs/fare.js index 4f349bd4..72707cf1 100644 --- a/src/routes/docs/fare.js +++ b/src/routes/docs/fare.js @@ -1,4 +1,3 @@ -const { response } = require("express"); const { objectIdPattern } = require("./utils"); const tag = "fare"; @@ -30,10 +29,12 @@ fareDocs[`${apiPrefix}/getTaxiFare`] = { 200: { description: "예상 택시 요금 반환 성공", content: { - "text/plain": { + "application/json": { schema: { - type: "number", - example: 10000, + type: "object", + properties: { + fare: { type: "number", example: 10000 }, + }, }, }, }, @@ -49,3 +50,5 @@ fareDocs[`${apiPrefix}/getTaxiFare`] = { }, }, }; + +module.exports = fareDocs; diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index 62639dfa..95fd8982 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -1,5 +1,6 @@ const { reportsSchema } = require("./schemas/reportsSchema"); const { roomsSchema } = require("./schemas/roomsSchema"); +const { fareSchema } = require("./schemas/fareSchema"); const reportsDocs = require("./reports"); const logininfoDocs = require("./logininfo"); const locationsDocs = require("./locations"); @@ -8,6 +9,7 @@ const authReplaceDocs = require("./auth.replace"); const usersDocs = require("./users"); const roomsDocs = require("./rooms"); const chatsDocs = require("./chats"); +const fareDocs = require("./fare"); const { port, nodeEnv } = require("../../../loadenv"); const serverList = [ @@ -68,6 +70,10 @@ const swaggerDocs = { name: "chats", description: "채팅 시 발생하는 이벤트 정리", }, + { + name: "fare", + description: "예상 택시 금액 계산", + }, ], consumes: ["application/json"], produces: ["application/json"], @@ -80,11 +86,13 @@ const swaggerDocs = { ...authReplaceDocs, ...chatsDocs, ...roomsDocs, + ...fareDocs, }, components: { schemas: { ...reportsSchema, ...roomsSchema, + ...fareSchema, }, }, }; From 3b7adfd4b1fe0a5f200d85374d4f92f4e91326d9 Mon Sep 17 00:00:00 2001 From: ybmin Date: Sun, 7 Jul 2024 18:58:13 +0900 Subject: [PATCH 24/33] Refactor: ts migration --- app.js | 2 +- package.json | 1 + pnpm-lock.yaml | 9 +- src/modules/{fare.js => fare.ts} | 63 ++++++-------- src/routes/fare.js | 9 -- src/routes/fare.ts | 11 +++ src/schedules/updateMajorTaxiFare.js | 14 ---- src/schedules/updateMajorTaxiFare.ts | 16 ++++ ...inorTaxiFare.js => updateMinorTaxiFare.ts} | 12 +-- src/services/{fare.js => fare.ts} | 82 +++++++++++-------- 10 files changed, 115 insertions(+), 104 deletions(-) rename src/modules/{fare.js => fare.ts} (60%) delete mode 100644 src/routes/fare.js create mode 100644 src/routes/fare.ts delete mode 100644 src/schedules/updateMajorTaxiFare.js create mode 100644 src/schedules/updateMajorTaxiFare.ts rename src/schedules/{updateMinorTaxiFare.js => updateMinorTaxiFare.ts} (59%) rename src/services/{fare.js => fare.ts} (75%) diff --git a/app.js b/app.js index 37ae32c1..4abe02ef 100644 --- a/app.js +++ b/app.js @@ -69,7 +69,7 @@ app.use("/chats", require("./src/routes/chats")); app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); -app.use("/fare", require("./src/routes/fare")); +app.use("/fare", require("./src/routes/fare.ts")); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/package.json b/package.json index 4ea2a648..ab15a691 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@adminjs/express": "^5.1.0", "@adminjs/mongoose": "^3.0.3", + "@types/express": "^4.17.21", "adminjs": "^6.8.7", "aws-sdk": "^2.1386.0", "axios": "^0.27.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0317beb7..de50fb41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@adminjs/mongoose': specifier: ^3.0.3 version: 3.0.3(adminjs@6.8.7)(mongoose@6.12.0) + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 adminjs: specifier: ^6.8.7 version: 6.8.7 @@ -3666,8 +3669,8 @@ packages: '@types/send': 0.17.1 dev: false - /@types/express@4.17.17: - resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} dependencies: '@types/body-parser': 1.19.2 '@types/express-serve-static-core': 4.17.35 @@ -5997,7 +6000,7 @@ packages: resolution: {integrity: sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==} engines: {node: '>=14'} dependencies: - '@types/express': 4.17.17 + '@types/express': 4.17.21 '@types/jsonwebtoken': 9.0.2 debug: 4.3.4 jose: 4.14.4 diff --git a/src/modules/fare.js b/src/modules/fare.ts similarity index 60% rename from src/modules/fare.js rename to src/modules/fare.ts index 8ea2e239..79334376 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.ts @@ -1,27 +1,26 @@ -const logger = require("./logger"); -const axios = require("axios"); +import axios, { AxiosRequestHeaders } from "axios"; -const { naverMapApiId, naverMapApiKey } = require("../../loadenv"); -const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); +import { naverMapApiId, naverMapApiKey } from "../../loadenv"; +import { taxiFareModel, locationModel } from "./stores/mongo"; -// Naver Cloud Platform Maps Directions 5 API Keys -const naverMapApi = { - "X-NCP-APIGW-API-KEY-ID": naverMapApiId, - "X-NCP-APIGW-API-KEY": naverMapApiKey, +interface TaxiFareInfo { + from: string; + to: string; + time: number; + fare: number; + isMajor: boolean; +} + +const naverMapApi: AxiosRequestHeaders = { + "X-NCP-APIGW-API-KEY-ID": naverMapApiId || "", + "X-NCP-APIGW-API-KEY": naverMapApiKey || "", }; const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; -// scaledTime에 사용하는 상수입니다. 0 ~ 47 (0:00 ~ 23:30) const timeConstants = 48; -/** - * 시간을 받아서 30분 단위로 변환해서 반환합니다. - * 요일 정보도 하나로 관리 - * @summary 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) - * @param {Date} time 변환할 시간 - */ -const scaledTime = (time) => { +const scaledTime = (time: Date): number => { return ( timeConstants * time.getDay() + time.getHours() * 2 + @@ -29,35 +28,27 @@ const scaledTime = (time) => { ); }; -/** Initialize database - * 1. Erase all previous data - * 2. Sets all taxi fare to 0 - * @summary 카이스트 본원 <-> 대전역의 경우 48 * 7개의 시간대에 대한 택시 요금을 init 시점의 비용으로 설정합니다. - * @summary 카이스트 본원 <-> 대전역 외 나머지 경로, 238개의 경로에 대해서는 한 개의 collection씩 설정하여 fare를 init 시점의 비용으로 설정합니다. time은 0으로 설정합니다. - */ -const initDatabase = async () => { +const initDatabase = async (): Promise => { try { if ( - !naverMapApi["X-NCP-APIGW-API-KEY"] || - !naverMapApi["X-NCP-APIGW-API-KEY-ID"] + naverMapApi["X-NCP-APIGW-API-KEY"] == "" || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" ) { - logger.log( + console.log( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); return; } - // Remove all previous data await taxiFareModel.deleteMany({}); const location = await locationModel.find({ isValid: { $eq: true } }); location.map(async (from) => { location.reduce(async (acc, to) => { - logger.info(`Initializing fare from ${from.koName} to ${to.koName}`); + console.log(`Initializing fare from ${from.koName} to ${to.koName}`); await acc; if (from._id === to._id) return; - let tableFare = []; - // 카이스트 본원 <-> 대전역의 경우 48*7(=336)개의 시간대에 대한 택시 요금을 일괄적으로 설정 + let tableFare: TaxiFareInfo[] = []; if (from.koName === "카이스트 본원" && to.koName === "대전역") { const fare = ( await axios.get( @@ -94,9 +85,7 @@ const initDatabase = async () => { isMajor: true, }); }); - } - // 카이스트 본원 <-> 대전역외의 경로(238개)에 대해서는 7개(일주일) 씩 collection 지정 설정 - else { + } else { await axios .get( `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ @@ -116,7 +105,7 @@ const initDatabase = async () => { }); }) .catch((err) => { - logger.error(err.message); + console.error(err.message); [...Array(7)].map((_, i) => { tableFare.push({ from: from, @@ -131,11 +120,11 @@ const initDatabase = async () => { await taxiFareModel.insertMany(tableFare); await new Promise((resolve) => setTimeout(resolve, 200)); return acc; - }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 + }, Promise.resolve()); }); } catch (err) { - logger.error("Error occured while initializing database: " + err.message); + console.error("Error occured while initializing database: " + err.message); } }; -module.exports = { scaledTime, initDatabase }; +export { scaledTime, initDatabase }; diff --git a/src/routes/fare.js b/src/routes/fare.js deleted file mode 100644 index 2d66e34a..00000000 --- a/src/routes/fare.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require("express"); -const { validateQuery } = require("../middlewares/zod"); -const { fareZod } = require("./docs/schemas/fareSchema"); -const { getTaxiFare } = require("../services/fare"); -const router = express.Router(); - -router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); - -module.exports = router; diff --git a/src/routes/fare.ts b/src/routes/fare.ts new file mode 100644 index 00000000..157c5baa --- /dev/null +++ b/src/routes/fare.ts @@ -0,0 +1,11 @@ +import express, { Router } from "express"; + +import { validateQuery } from "../middlewares/zod"; +import { fareZod } from "./docs/schemas/fareSchema"; +import { getTaxiFare } from "../services/fare"; + +const router: Router = express.Router(); + +router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); + +export default router; diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js deleted file mode 100644 index abaff9d9..00000000 --- a/src/schedules/updateMajorTaxiFare.js +++ /dev/null @@ -1,14 +0,0 @@ -const { scaledTime } = require("../modules/fare"); -const logger = require("../modules/logger"); -const { updateTaxiFare } = require("../services/fare"); - -/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ -module.exports = (app) => async () => { - try { - time = new Date(); - sTime = scaledTime(time); - await updateTaxiFare(sTime, true); - } catch (err) { - logger.error(err); - } -}; diff --git a/src/schedules/updateMajorTaxiFare.ts b/src/schedules/updateMajorTaxiFare.ts new file mode 100644 index 00000000..43e4ff64 --- /dev/null +++ b/src/schedules/updateMajorTaxiFare.ts @@ -0,0 +1,16 @@ +import logger from "../modules/logger"; + +import { updateTaxiFare } from "../services/fare"; +import { scaledTime } from "../modules/fare"; + +/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ +export default (app: any): any => + async (): Promise => { + try { + const time: Date = new Date(); + const sTime: number = scaledTime(time); + await updateTaxiFare(sTime, true); + } catch (err) { + logger.error(err); + } + }; diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.ts similarity index 59% rename from src/schedules/updateMinorTaxiFare.js rename to src/schedules/updateMinorTaxiFare.ts index b5201712..a88cc570 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.ts @@ -1,11 +1,13 @@ -const { updateTaxiFare } = require("../services/fare"); +import logger from "../modules/logger"; + +import { updateTaxiFare } from "../services/fare"; /* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 1주일 단위로 캐싱합니다. */ -module.exports = (app) => async () => { +export default (app: any): any => async (): Promise => { try { - const date = new Date(); + const date: Date = new Date(); await updateTaxiFare(48 * date.getDay(), false); // 18:00시의 택시 요금이지만 db에는 48*(요일) + 0으로 저장됨 - } catch (err) { + } catch (err: any) { logger.error(err); } -}; +}; \ No newline at end of file diff --git a/src/services/fare.js b/src/services/fare.ts similarity index 75% rename from src/services/fare.js rename to src/services/fare.ts index 534db849..3befa515 100644 --- a/src/services/fare.js +++ b/src/services/fare.ts @@ -1,14 +1,15 @@ -const axios = require("axios"); +import axios, { AxiosRequestHeaders } from "axios"; +import { Request, Response } from "express"; +import logger from "../modules/logger"; -const { naverMapApiId, naverMapApiKey } = require("../../loadenv"); -const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); -const { scaledTime } = require("../modules/fare"); -const logger = require("../modules/logger"); +import { naverMapApiId, naverMapApiKey } from "../../loadenv"; +import { taxiFareModel, locationModel } from "../modules/stores/mongo"; +import { scaledTime } from "../modules/fare"; // Naver Cloud Platform Maps Directions 5 API Keys -const naverMapApi = { - "X-NCP-APIGW-API-KEY-ID": naverMapApiId, - "X-NCP-APIGW-API-KEY": naverMapApiKey, +const naverMapApi: AxiosRequestHeaders = { + "X-NCP-APIGW-API-KEY-ID": naverMapApiId || "", + "X-NCP-APIGW-API-KEY": naverMapApiKey || "", }; const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; @@ -22,13 +23,13 @@ const naverMapApiCall = * - @param {mongoose.Schema.Types.ObjectId} to - 도착지 * - @param {Date} time - 출발 시간 (ISO 8601) */ -const getTaxiFare = async (req, res) => { +const getTaxiFare = async (req: Request, res: Response): Promise => { try { if ( - !naverMapApi["X-NCP-APIGW-API-KEY"] || - !naverMapApi["X-NCP-APIGW-API-KEY-ID"] + naverMapApi["X-NCP-APIGW-API-KEY"] == "" || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" ) { - logger.log( + logger.info( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); res @@ -44,7 +45,7 @@ const getTaxiFare = async (req, res) => { const to = await locationModel .findOne({ _id: { $eq: req.query.to } }) .clone(); - const sTime = scaledTime(new Date(req.query.time)); + const sTime = scaledTime(new Date(req.query.time as string)); if (!from || !to) { res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); @@ -55,7 +56,7 @@ const getTaxiFare = async (req, res) => { .findOne( { from: from._id, to: to._id, time: 0 }, { isMajor: true }, - (err, docs) => { + (err: Error, docs: any) => { if (err) logger.error( "Error occured while finding TaxiFare documents: " + err.message @@ -73,7 +74,7 @@ const getTaxiFare = async (req, res) => { to: to._id, time: sTime, }, - (err, docs) => { + (err: Error, docs: any) => { if (err) logger.error( "Error occured while finding TaxiFare documents: " + err.message @@ -109,7 +110,7 @@ const getTaxiFare = async (req, res) => { to: to._id, time: 0, }, - (err, docs) => { + (err: Error, docs: any) => { if (err) logger.error( "Error occured while finding TaxiFare documents: " + err.message @@ -150,10 +151,22 @@ const getTaxiFare = async (req, res) => { * 주어진 from, to, sTime에 대한 단일 택시 요금을 업데이트합니다. * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. - * @param {Date} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) + * @param {number} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 여부 */ -const updateTaxiFare = async (sTime, isMajor) => { +const updateTaxiFare = async ( + sTime: number, + isMajor: boolean +): Promise => { + if ( + naverMapApi["X-NCP-APIGW-API-KEY"] == "" || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" + ) { + logger.info( + "There is no credential for Naver Map. Taxi Fare functions are disabled." + ); + return; + } const prevFares = await taxiFareModel .find({ time: sTime, @@ -176,19 +189,21 @@ const updateTaxiFare = async (sTime, isMajor) => { logger.error(err.message); }) .then(async (res) => { - await taxiFareModel - .updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: res.data.route.traoptimal[0].summary.taxiFare }, - (err, docs) => { - if (err) - logger.error( - "Error occured while updating TaxiFare document: " + - err.message - ); - } - ) - .clone(); + if (res && res.data) { + await taxiFareModel + .updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: res.data.route.traoptimal[0].summary.taxiFare }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while updating TaxiFare document: " + + err.message + ); + } + ) + .clone(); + } }) .catch((err) => { logger.error(err.message); @@ -198,7 +213,4 @@ const updateTaxiFare = async (sTime, isMajor) => { }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 }; -module.exports = { - getTaxiFare, - updateTaxiFare, -}; +export { getTaxiFare, updateTaxiFare }; From e1b82cbef563c5b5072bcb5058826c66d4b209b9 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 9 Jul 2024 20:35:29 +0900 Subject: [PATCH 25/33] Add: non credential test case execption --- loadenv.js | 6 +- src/modules/fare.ts | 173 +++++++++++------------ src/schedules/index.js | 7 +- src/services/fare.ts | 308 ++++++++++++++++++++--------------------- 4 files changed, 251 insertions(+), 243 deletions(-) diff --git a/loadenv.js b/loadenv.js index 930c2982..a1335db2 100644 --- a/loadenv.js +++ b/loadenv.js @@ -44,6 +44,8 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional - naverMapApiId: process.env.NAVER_MAP_API_ID, // optional - naverMapApiKey: process.env.NAVER_MAP_API_KEY, //optional + naverMap: { + naverMapApiId: process.env.NAVER_MAP_API_ID || false, // optional + naverMapApiKey: process.env.NAVER_MAP_API_KEY || false, //optional + }, }; diff --git a/src/modules/fare.ts b/src/modules/fare.ts index 79334376..71473273 100644 --- a/src/modules/fare.ts +++ b/src/modules/fare.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestHeaders } from "axios"; +import logger from "../modules/logger"; -import { naverMapApiId, naverMapApiKey } from "../../loadenv"; +import { naverMap } from "../../loadenv"; import { taxiFareModel, locationModel } from "./stores/mongo"; interface TaxiFareInfo { @@ -12,8 +13,8 @@ interface TaxiFareInfo { } const naverMapApi: AxiosRequestHeaders = { - "X-NCP-APIGW-API-KEY-ID": naverMapApiId || "", - "X-NCP-APIGW-API-KEY": naverMapApiKey || "", + "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, + "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, }; const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; @@ -31,99 +32,101 @@ const scaledTime = (time: Date): number => { const initDatabase = async (): Promise => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == "" || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" + naverMapApi["X-NCP-APIGW-API-KEY"] == false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false ) { - console.log( + logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); - return; - } - await taxiFareModel.deleteMany({}); + } else { + await taxiFareModel.deleteMany({}); - const location = await locationModel.find({ isValid: { $eq: true } }); + const location = await locationModel.find({ isValid: { $eq: true } }); - location.map(async (from) => { - location.reduce(async (acc, to) => { - console.log(`Initializing fare from ${from.koName} to ${to.koName}`); - await acc; - if (from._id === to._id) return; - let tableFare: TaxiFareInfo[] = []; - if (from.koName === "카이스트 본원" && to.koName === "대전역") { - const fare = ( - await axios.get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, + location.map(async (from) => { + location.reduce(async (acc, to) => { + await acc; + if (from._id === to._id) return; + let tableFare: TaxiFareInfo[] = []; + if (from.koName === "카이스트 본원" && to.koName === "대전역") { + const fare = ( + await axios.get( + `${ + naverMapApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, + }); }); - }); - } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - const fare = ( - await axios.get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, + } else if ( + from.koName === "대전역" && + to.koName === "카이스트 본원" + ) { + const fare = ( + await axios.get( + `${ + naverMapApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, + }); }); - }); - } else { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((res) => { - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * timeConstants, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: false, + } else { + await axios + .get( + `${ + naverMapApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverMapApi } + ) + .then((res) => { + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * timeConstants, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: false, + }); }); - }); - }) - .catch((err) => { - console.error(err.message); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * timeConstants, - fare: 0, - isMajor: false, + }) + .catch((err) => { + logger.error(err.message); + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * timeConstants, + fare: 0, + isMajor: false, + }); }); }); - }); - } - await taxiFareModel.insertMany(tableFare); - await new Promise((resolve) => setTimeout(resolve, 200)); - return acc; - }, Promise.resolve()); - }); + } + await taxiFareModel.insertMany(tableFare); + await new Promise((resolve) => setTimeout(resolve, 200)); + return acc; + }, Promise.resolve()); + }); + } } catch (err) { - console.error("Error occured while initializing database: " + err.message); + logger.error("Error occured while initializing database: " + err.message); } }; diff --git a/src/schedules/index.js b/src/schedules/index.js index fda57d53..f4996ab0 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -1,10 +1,13 @@ const cron = require("node-cron"); +const { naverMapApiId, naverMapApiKey } = require("../../loadenv").naverMap; const registerSchedules = (app) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); - cron.schedule("0,30 * * * * ", require("./updateMajorTaxiFare")(app)); - cron.schedule("0 18 * * *", require("./updateMinorTaxiFare")(app)); + if (naverMapApiId != false && naverMapApiKey != false) { + cron.schedule("0,30 * * * * ", require("./updateMajorTaxiFare")(app)); + cron.schedule("0 18 * * *", require("./updateMinorTaxiFare")(app)); + } }; module.exports = registerSchedules; diff --git a/src/services/fare.ts b/src/services/fare.ts index 3befa515..f70dd4e2 100644 --- a/src/services/fare.ts +++ b/src/services/fare.ts @@ -2,14 +2,14 @@ import axios, { AxiosRequestHeaders } from "axios"; import { Request, Response } from "express"; import logger from "../modules/logger"; -import { naverMapApiId, naverMapApiKey } from "../../loadenv"; +import { naverMap } from "../../loadenv"; import { taxiFareModel, locationModel } from "../modules/stores/mongo"; import { scaledTime } from "../modules/fare"; // Naver Cloud Platform Maps Directions 5 API Keys const naverMapApi: AxiosRequestHeaders = { - "X-NCP-APIGW-API-KEY-ID": naverMapApiId || "", - "X-NCP-APIGW-API-KEY": naverMapApiKey || "", + "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, + "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, }; const naverMapApiCall = "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; @@ -26,124 +26,123 @@ const naverMapApiCall = const getTaxiFare = async (req: Request, res: Response): Promise => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == "" || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" + naverMapApi["X-NCP-APIGW-API-KEY"] == false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false ) { - logger.info( - "There is no credential for Naver Map. Taxi Fare functions are disabled." - ); res .status(503) - .json({ error: "fare/getTaxiFare: Naver Cloud API not found" }); - return; + .json({ error: "fare/getTaxiFare: Naver Map API credential not found" }); } - const from = await locationModel - .findOne({ - _id: { $eq: req.query.from }, - }) - .clone(); - const to = await locationModel - .findOne({ _id: { $eq: req.query.to } }) - .clone(); - const sTime = scaledTime(new Date(req.query.time as string)); + else{ - if (!from || !to) { - res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); - return; - } - const isMajor = ( - await taxiFareModel - .findOne( - { from: from._id, to: to._id, time: 0 }, - { isMajor: true }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding TaxiFare documents: " + err.message - ); - } - ) - .clone() - ).isMajor; - // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) - if (isMajor) { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: sTime, - }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding TaxiFare documents: " + err.message - ); - } - ) + const from = await locationModel + .findOne({ + _id: { $eq: req.query.from }, + }) .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); - }) - .catch((err) => { - logger.error(err.message); - }); - } else { - res.status(200).json({ fare: taxiFare.fare }); - } - } else { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: 0, - }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding TaxiFare documents: " + err.message - ); - } - ) + const to = await locationModel + .findOne({ _id: { $eq: req.query.to } }) .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } + const sTime = scaledTime(new Date(req.query.time as string)); + + if (!from || !to) { + res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); + return; + } + const isMajor = ( + await taxiFareModel + .findOne( + { from: from._id, to: to._id, time: 0 }, + { isMajor: true }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); - }) - .catch((err) => { - logger.error(err.message); - }); + .clone() + ).isMajor; + // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) + if (isMajor) { + const taxiFare = await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + time: sTime, + }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } + ) + .clone(); + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + if (!taxiFare || taxiFare.fare <= 0) { + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); + } else { + res.status(200).json({ fare: taxiFare.fare }); + } } else { - res.status(200).json({ fare: taxiFare.fare }); + const taxiFare = await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + time: 0, + }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } + ) + .clone(); + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + if (!taxiFare || taxiFare.fare <= 0) { + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); + } else { + res.status(200).json({ fare: taxiFare.fare }); + } } } } catch (err) { logger.error(err.message); res .status(500) - .json({ error: "fare/getTaxiFare: Failed to load taxi fare" }); + .json({ error: "fare/getTaxiFare: Failed to load Taxi Fare" }); } }; @@ -152,65 +151,66 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. * @param {number} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) - * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 여부 + * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 경로 / 이외 경로 */ const updateTaxiFare = async ( sTime: number, isMajor: boolean ): Promise => { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == "" || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == "" + naverMapApi["X-NCP-APIGW-API-KEY"] == false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false ) { - logger.info( + logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); - return; } - const prevFares = await taxiFareModel - .find({ - time: sTime, - isMajor: isMajor, - }) - .clone(); - await prevFares.reduce(async (acc, item) => { - const from = await locationModel.findOne({ _id: item.from }).clone(); - const to = await locationModel.findOne({ _id: item.to }).clone(); - - await acc; - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .catch((err) => { - logger.error(err.message); + else{ + const prevFares = await taxiFareModel + .find({ + time: sTime, + isMajor: isMajor, }) - .then(async (res) => { - if (res && res.data) { - await taxiFareModel - .updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: res.data.route.traoptimal[0].summary.taxiFare }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while updating TaxiFare document: " + - err.message - ); - } - ) - .clone(); - } - }) - .catch((err) => { - logger.error(err.message); - }); - await new Promise((resolve) => setTimeout(() => resolve, 200)); - return acc; - }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 + .clone(); + await prevFares.reduce(async (acc, item) => { + const from = await locationModel.findOne({ _id: item.from }).clone(); + const to = await locationModel.findOne({ _id: item.to }).clone(); + + await acc; + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + .catch((err) => { + logger.error(err.message); + }) + .then(async (res) => { + if (res && res.data) { + await taxiFareModel + .updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: res.data.route.traoptimal[0].summary.taxiFare }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while updating Taxi Fare document: " + + err.message + ); + } + ) + .clone(); + } + }) + .catch((err) => { + logger.error(err.message); + }); + await new Promise((resolve) => setTimeout(() => resolve, 200)); + return acc; + }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 + } }; export { getTaxiFare, updateTaxiFare }; From f36c436035bfbd7378416bf348b86ec32e5e3c44 Mon Sep 17 00:00:00 2001 From: ybmin Date: Tue, 9 Jul 2024 22:10:15 +0900 Subject: [PATCH 26/33] Fix: code convention --- src/modules/fare.ts | 163 ++++++++++++++++---------------- src/schedules/index.js | 2 +- src/services/fare.ts | 205 ++++++++++++++++++++--------------------- 3 files changed, 181 insertions(+), 189 deletions(-) diff --git a/src/modules/fare.ts b/src/modules/fare.ts index 71473273..02b9b6e0 100644 --- a/src/modules/fare.ts +++ b/src/modules/fare.ts @@ -32,99 +32,94 @@ const scaledTime = (time: Date): number => { const initDatabase = async (): Promise => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false + naverMapApi["X-NCP-APIGW-API-KEY"] === false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); - } else { - await taxiFareModel.deleteMany({}); - - const location = await locationModel.find({ isValid: { $eq: true } }); - - location.map(async (from) => { - location.reduce(async (acc, to) => { - await acc; - if (from._id === to._id) return; - let tableFare: TaxiFareInfo[] = []; - if (from.koName === "카이스트 본원" && to.koName === "대전역") { - const fare = ( - await axios.get( - `${ - naverMapApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, - }); + return; + } + await taxiFareModel.deleteMany({}); + const location = await locationModel.find({ isValid: { $eq: true } }); + location.map(async (from) => { + location.reduce(async (acc, to) => { + await acc; + if (from._id === to._id) return; + let tableFare: TaxiFareInfo[] = []; + if (from.koName === "카이스트 본원" && to.koName === "대전역") { + const fare = ( + await axios.get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, }); - } else if ( - from.koName === "대전역" && - to.koName === "카이스트 본원" - ) { - const fare = ( - await axios.get( - `${ - naverMapApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, - }); + }); + } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { + const fare = ( + await axios.get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + from: from._id, + to: to._id, + time: i, + fare: fare, + isMajor: true, }); - } else { - await axios - .get( - `${ - naverMapApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverMapApi } - ) - .then((res) => { - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * timeConstants, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: false, - }); + }); + } else { + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + .then((res) => { + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * timeConstants, + fare: res.data.route.traoptimal[0].summary.taxiFare, + isMajor: false, }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * timeConstants, - fare: 0, - isMajor: false, - }); + }); + }) + .catch((err) => { + logger.error(err.message); + [...Array(7)].map((_, i) => { + tableFare.push({ + from: from, + to: to, + time: i * timeConstants, + fare: 0, + isMajor: false, }); }); - } - await taxiFareModel.insertMany(tableFare); - await new Promise((resolve) => setTimeout(resolve, 200)); - return acc; - }, Promise.resolve()); - }); - } + }); + } + await taxiFareModel.insertMany(tableFare); + await new Promise((resolve) => setTimeout(resolve, 200)); + return acc; + }, Promise.resolve()); + }); } catch (err) { logger.error("Error occured while initializing database: " + err.message); } diff --git a/src/schedules/index.js b/src/schedules/index.js index f4996ab0..76eeac10 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -4,7 +4,7 @@ const { naverMapApiId, naverMapApiKey } = require("../../loadenv").naverMap; const registerSchedules = (app) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); - if (naverMapApiId != false && naverMapApiKey != false) { + if (naverMapApiId !== false && naverMapApiKey !== false) { cron.schedule("0,30 * * * * ", require("./updateMajorTaxiFare")(app)); cron.schedule("0 18 * * *", require("./updateMinorTaxiFare")(app)); } diff --git a/src/services/fare.ts b/src/services/fare.ts index f70dd4e2..2f432eba 100644 --- a/src/services/fare.ts +++ b/src/services/fare.ts @@ -26,116 +26,114 @@ const naverMapApiCall = const getTaxiFare = async (req: Request, res: Response): Promise => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false + naverMapApi["X-NCP-APIGW-API-KEY"] === false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false ) { res .status(503) .json({ error: "fare/getTaxiFare: Naver Map API credential not found" }); + return; } - else{ + const from = await locationModel + .findOne({ + _id: { $eq: req.query.from }, + }) + .clone(); + const to = await locationModel + .findOne({ _id: { $eq: req.query.to } }) + .clone(); + const sTime = scaledTime(new Date(req.query.time as string)); - const from = await locationModel - .findOne({ - _id: { $eq: req.query.from }, - }) - .clone(); - const to = await locationModel - .findOne({ _id: { $eq: req.query.to } }) + if (!from || !to) { + res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); + return; + } + const isMajor = ( + await taxiFareModel + .findOne( + { from: from._id, to: to._id, time: 0 }, + { isMajor: true }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } + ) + .clone() + ).isMajor; + // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) + if (isMajor) { + const taxiFare = await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + time: sTime, + }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } + ) .clone(); - const sTime = scaledTime(new Date(req.query.time as string)); - - if (!from || !to) { - res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); - return; - } - const isMajor = ( - await taxiFareModel - .findOne( - { from: from._id, to: to._id, time: 0 }, - { isMajor: true }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + err.message - ); - } - ) - .clone() - ).isMajor; - // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) - if (isMajor) { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: sTime, - }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + err.message - ); - } + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + if (!taxiFare || taxiFare.fare <= 0) { + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } ) - .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); - }) - .catch((err) => { - logger.error(err.message); - }); - } else { - res.status(200).json({ fare: taxiFare.fare }); - } + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); } else { - const taxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - time: 0, - }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while finding Taxi Fare documents: " + err.message - ); - } + res.status(200).json({ fare: taxiFare.fare }); + } + } else { + const taxiFare = await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + time: 0, + }, + (err: Error, docs: any) => { + if (err) + logger.error( + "Error occured while finding Taxi Fare documents: " + err.message + ); + } + ) + .clone(); + //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + if (!taxiFare || taxiFare.fare <= 0) { + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } ) - .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 - if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); - }) - .catch((err) => { - logger.error(err.message); - }); - } else { - res.status(200).json({ fare: taxiFare.fare }); - } + .then((text) => { + res + .status(200) + .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + }) + .catch((err) => { + logger.error(err.message); + }); + } else { + res.status(200).json({ fare: taxiFare.fare }); } } } catch (err) { @@ -158,14 +156,14 @@ const updateTaxiFare = async ( isMajor: boolean ): Promise => { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] == false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] == false + naverMapApi["X-NCP-APIGW-API-KEY"] === false || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); + return; } - else{ const prevFares = await taxiFareModel .find({ time: sTime, @@ -210,7 +208,6 @@ const updateTaxiFare = async ( await new Promise((resolve) => setTimeout(() => resolve, 200)); return acc; }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 - } }; export { getTaxiFare, updateTaxiFare }; From 57d12376c280de26f4443aba362f1b85404e6c5c Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 18 Jul 2024 19:33:02 +0900 Subject: [PATCH 27/33] Revert: ts to js --- app.js | 2 +- src/modules/{fare.ts => fare.js} | 26 ++-- src/routes/fare.js | 11 ++ src/routes/fare.ts | 11 -- src/schedules/updateMajorTaxiFare.js | 15 ++ src/schedules/updateMajorTaxiFare.ts | 16 --- ...inorTaxiFare.ts => updateMinorTaxiFare.js} | 12 +- src/services/{fare.ts => fare.js} | 131 +++++++++--------- 8 files changed, 107 insertions(+), 117 deletions(-) rename src/modules/{fare.ts => fare.js} (86%) create mode 100644 src/routes/fare.js delete mode 100644 src/routes/fare.ts create mode 100644 src/schedules/updateMajorTaxiFare.js delete mode 100644 src/schedules/updateMajorTaxiFare.ts rename src/schedules/{updateMinorTaxiFare.ts => updateMinorTaxiFare.js} (59%) rename src/services/{fare.ts => fare.js} (68%) diff --git a/app.js b/app.js index 4abe02ef..37ae32c1 100644 --- a/app.js +++ b/app.js @@ -69,7 +69,7 @@ app.use("/chats", require("./src/routes/chats")); app.use("/locations", require("./src/routes/locations")); app.use("/reports", require("./src/routes/reports")); app.use("/notifications", require("./src/routes/notifications")); -app.use("/fare", require("./src/routes/fare.ts")); +app.use("/fare", require("./src/routes/fare")); // [Middleware] 전역 에러 핸들러. 에러 핸들러는 router들보다 아래에 등록되어야 합니다. app.use(require("./src/middlewares/errorHandler")); diff --git a/src/modules/fare.ts b/src/modules/fare.js similarity index 86% rename from src/modules/fare.ts rename to src/modules/fare.js index 02b9b6e0..5fcecb8c 100644 --- a/src/modules/fare.ts +++ b/src/modules/fare.js @@ -1,18 +1,10 @@ -import axios, { AxiosRequestHeaders } from "axios"; -import logger from "../modules/logger"; +const axios = require("axios"); +const logger = require("./logger"); -import { naverMap } from "../../loadenv"; -import { taxiFareModel, locationModel } from "./stores/mongo"; +const { naverMap } = require("../../loadenv"); +const { taxiFareModel, locationModel } = require("./stores/mongo"); -interface TaxiFareInfo { - from: string; - to: string; - time: number; - fare: number; - isMajor: boolean; -} - -const naverMapApi: AxiosRequestHeaders = { +const naverMapApi = { "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, }; @@ -21,7 +13,7 @@ const naverMapApiCall = const timeConstants = 48; -const scaledTime = (time: Date): number => { +const scaledTime = (time) => { return ( timeConstants * time.getDay() + time.getHours() * 2 + @@ -29,7 +21,7 @@ const scaledTime = (time: Date): number => { ); }; -const initDatabase = async (): Promise => { +const initDatabase = async () => { try { if ( naverMapApi["X-NCP-APIGW-API-KEY"] === false || @@ -46,7 +38,7 @@ const initDatabase = async (): Promise => { location.reduce(async (acc, to) => { await acc; if (from._id === to._id) return; - let tableFare: TaxiFareInfo[] = []; + let tableFare = []; if (from.koName === "카이스트 본원" && to.koName === "대전역") { const fare = ( await axios.get( @@ -125,4 +117,4 @@ const initDatabase = async (): Promise => { } }; -export { scaledTime, initDatabase }; +module.exports = { scaledTime, initDatabase }; diff --git a/src/routes/fare.js b/src/routes/fare.js new file mode 100644 index 00000000..d4c6e2aa --- /dev/null +++ b/src/routes/fare.js @@ -0,0 +1,11 @@ +const express = require("express"); + +const { validateQuery } = require("../middlewares/zod"); +const { fareZod } = require("./docs/schemas/fareSchema"); +const { getTaxiFare } = require("../services/fare"); + +const router = express.Router(); + +router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); + +module.exports = router; diff --git a/src/routes/fare.ts b/src/routes/fare.ts deleted file mode 100644 index 157c5baa..00000000 --- a/src/routes/fare.ts +++ /dev/null @@ -1,11 +0,0 @@ -import express, { Router } from "express"; - -import { validateQuery } from "../middlewares/zod"; -import { fareZod } from "./docs/schemas/fareSchema"; -import { getTaxiFare } from "../services/fare"; - -const router: Router = express.Router(); - -router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); - -export default router; diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js new file mode 100644 index 00000000..57446525 --- /dev/null +++ b/src/schedules/updateMajorTaxiFare.js @@ -0,0 +1,15 @@ +const logger = require("../modules/logger"); + +const { updateTaxiFare } = require("../services/fare"); +const { scaledTime } = require("../modules/fare"); + +/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ +module.exports = (app) => async () => { + try { + const time = new Date(); + const sTime = scaledTime(time); + await updateTaxiFare(sTime, true); + } catch (err) { + logger.error(err); + } +}; diff --git a/src/schedules/updateMajorTaxiFare.ts b/src/schedules/updateMajorTaxiFare.ts deleted file mode 100644 index 43e4ff64..00000000 --- a/src/schedules/updateMajorTaxiFare.ts +++ /dev/null @@ -1,16 +0,0 @@ -import logger from "../modules/logger"; - -import { updateTaxiFare } from "../services/fare"; -import { scaledTime } from "../modules/fare"; - -/* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ -export default (app: any): any => - async (): Promise => { - try { - const time: Date = new Date(); - const sTime: number = scaledTime(time); - await updateTaxiFare(sTime, true); - } catch (err) { - logger.error(err); - } - }; diff --git a/src/schedules/updateMinorTaxiFare.ts b/src/schedules/updateMinorTaxiFare.js similarity index 59% rename from src/schedules/updateMinorTaxiFare.ts rename to src/schedules/updateMinorTaxiFare.js index a88cc570..52ba53f3 100644 --- a/src/schedules/updateMinorTaxiFare.ts +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,13 +1,13 @@ -import logger from "../modules/logger"; +const logger = require("../modules/logger"); -import { updateTaxiFare } from "../services/fare"; +const { updateTaxiFare } = require("../services/fare"); /* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 1주일 단위로 캐싱합니다. */ -export default (app: any): any => async (): Promise => { +module.exports = (app) => async () => { try { - const date: Date = new Date(); + const date = new Date(); await updateTaxiFare(48 * date.getDay(), false); // 18:00시의 택시 요금이지만 db에는 48*(요일) + 0으로 저장됨 - } catch (err: any) { + } catch (err) { logger.error(err); } -}; \ No newline at end of file +}; diff --git a/src/services/fare.ts b/src/services/fare.js similarity index 68% rename from src/services/fare.ts rename to src/services/fare.js index 2f432eba..b6fb10a1 100644 --- a/src/services/fare.ts +++ b/src/services/fare.js @@ -1,13 +1,12 @@ -import axios, { AxiosRequestHeaders } from "axios"; -import { Request, Response } from "express"; -import logger from "../modules/logger"; +const axios = require("axios"); +const logger = require("../modules/logger"); -import { naverMap } from "../../loadenv"; -import { taxiFareModel, locationModel } from "../modules/stores/mongo"; -import { scaledTime } from "../modules/fare"; +const { naverMap } = require("../../loadenv"); +const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); +const { scaledTime } = require("../modules/fare"); // Naver Cloud Platform Maps Directions 5 API Keys -const naverMapApi: AxiosRequestHeaders = { +const naverMapApi = { "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, }; @@ -23,15 +22,15 @@ const naverMapApiCall = * - @param {mongoose.Schema.Types.ObjectId} to - 도착지 * - @param {Date} time - 출발 시간 (ISO 8601) */ -const getTaxiFare = async (req: Request, res: Response): Promise => { +const getTaxiFare = async (req, res) => { try { if ( naverMapApi["X-NCP-APIGW-API-KEY"] === false || naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false ) { - res - .status(503) - .json({ error: "fare/getTaxiFare: Naver Map API credential not found" }); + res.status(503).json({ + error: "fare/getTaxiFare: Naver Map API credential not found", + }); return; } const from = await locationModel @@ -42,7 +41,7 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { const to = await locationModel .findOne({ _id: { $eq: req.query.to } }) .clone(); - const sTime = scaledTime(new Date(req.query.time as string)); + const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); @@ -53,10 +52,11 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { .findOne( { from: from._id, to: to._id, time: 0 }, { isMajor: true }, - (err: Error, docs: any) => { + (err, docs) => { if (err) logger.error( - "Error occured while finding Taxi Fare documents: " + err.message + "Error occured while finding Taxi Fare documents: " + + err.message ); } ) @@ -71,10 +71,11 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { to: to._id, time: sTime, }, - (err: Error, docs: any) => { + (err, docs) => { if (err) logger.error( - "Error occured while finding Taxi Fare documents: " + err.message + "Error occured while finding Taxi Fare documents: " + + err.message ); } ) @@ -107,10 +108,11 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { to: to._id, time: 0, }, - (err: Error, docs: any) => { + (err, docs) => { if (err) logger.error( - "Error occured while finding Taxi Fare documents: " + err.message + "Error occured while finding Taxi Fare documents: " + + err.message ); } ) @@ -151,10 +153,7 @@ const getTaxiFare = async (req: Request, res: Response): Promise => { * @param {number} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 경로 / 이외 경로 */ -const updateTaxiFare = async ( - sTime: number, - isMajor: boolean -): Promise => { +const updateTaxiFare = async (sTime, isMajor) => { if ( naverMapApi["X-NCP-APIGW-API-KEY"] === false || naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false @@ -164,50 +163,50 @@ const updateTaxiFare = async ( ); return; } - const prevFares = await taxiFareModel - .find({ - time: sTime, - isMajor: isMajor, + const prevFares = await taxiFareModel + .find({ + time: sTime, + isMajor: isMajor, + }) + .clone(); + await prevFares.reduce(async (acc, item) => { + const from = await locationModel.findOne({ _id: item.from }).clone(); + const to = await locationModel.findOne({ _id: item.to }).clone(); + + await acc; + await axios + .get( + `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ + to.longitude + "," + to.latitude + }&options=traoptimal`, + { headers: naverMapApi } + ) + .catch((err) => { + logger.error(err.message); }) - .clone(); - await prevFares.reduce(async (acc, item) => { - const from = await locationModel.findOne({ _id: item.from }).clone(); - const to = await locationModel.findOne({ _id: item.to }).clone(); - - await acc; - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .catch((err) => { - logger.error(err.message); - }) - .then(async (res) => { - if (res && res.data) { - await taxiFareModel - .updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: res.data.route.traoptimal[0].summary.taxiFare }, - (err: Error, docs: any) => { - if (err) - logger.error( - "Error occured while updating Taxi Fare document: " + - err.message - ); - } - ) - .clone(); - } - }) - .catch((err) => { - logger.error(err.message); - }); - await new Promise((resolve) => setTimeout(() => resolve, 200)); - return acc; - }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 + .then(async (res) => { + if (res && res.data) { + await taxiFareModel + .updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: res.data.route.traoptimal[0].summary.taxiFare }, + (err, docs) => { + if (err) + logger.error( + "Error occured while updating Taxi Fare document: " + + err.message + ); + } + ) + .clone(); + } + }) + .catch((err) => { + logger.error(err.message); + }); + await new Promise((resolve) => setTimeout(() => resolve, 200)); + return acc; + }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 }; -export { getTaxiFare, updateTaxiFare }; +module.exports = { getTaxiFare, updateTaxiFare }; From a69f0fd06c53359cd2faafc4b78eb9bffd94ef68 Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 18 Jul 2024 21:50:30 +0900 Subject: [PATCH 28/33] =?UTF-8?q?Add:=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=EC=8B=9C=20=EB=B9=88=20=ED=95=84=EB=93=9C=EB=A7=8C=20=EC=B1=84?= =?UTF-8?q?=EC=9B=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/fare.js | 108 ++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index 5fcecb8c..63ebdb2a 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -32,82 +32,80 @@ const initDatabase = async () => { ); return; } - await taxiFareModel.deleteMany({}); const location = await locationModel.find({ isValid: { $eq: true } }); location.map(async (from) => { location.reduce(async (acc, to) => { await acc; if (from._id === to._id) return; let tableFare = []; + const prevTaxiFare = await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + }, + { fare: true } + ) + .clone().fare; + const fare = prevTaxiFare + ? prevTaxiFare + : ( + await axios.get( + `${ + naverMapApiCall + from.longitude + "," + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; if (from.koName === "카이스트 본원" && to.koName === "대전역") { - const fare = ( - await axios.get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; [...Array(timeConstants * 7)].map((_, i) => { tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, + updateOne: { + filter: { from: from._id, to: to._id, time: i, isMajor: true }, + update: { + $setOnInsert: { + fare: fare, + }, + }, + upsert: true, + }, }); }); } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - const fare = ( - await axios.get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; [...Array(timeConstants * 7)].map((_, i) => { tableFare.push({ - from: from._id, - to: to._id, - time: i, - fare: fare, - isMajor: true, + updateOne: { + filter: { from: from._id, to: to._id, time: i, isMajor: true }, + update: { + $setOnInsert: { + fare: fare, + }, + }, + upsert: true, + }, }); }); } else { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((res) => { - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, - time: i * timeConstants, - fare: res.data.route.traoptimal[0].summary.taxiFare, - isMajor: false, - }); - }); - }) - .catch((err) => { - logger.error(err.message); - [...Array(7)].map((_, i) => { - tableFare.push({ - from: from, - to: to, + [...Array(7)].map((_, i) => { + tableFare.push({ + updateOne: { + filter: { + from: from._id, + to: to._id, time: i * timeConstants, - fare: 0, isMajor: false, - }); - }); + }, + update: { + $setOnInsert: { + fare: fare, + }, + }, + upsert: true, + }, }); + }); } - await taxiFareModel.insertMany(tableFare); + await taxiFareModel.bulkWrite(tableFare); await new Promise((resolve) => setTimeout(resolve, 200)); return acc; }, Promise.resolve()); From c7a8d29a5242b54a24983a69f17cc22cf09b3de2 Mon Sep 17 00:00:00 2001 From: ybmin Date: Thu, 18 Jul 2024 22:03:38 +0900 Subject: [PATCH 29/33] Fix: undefined error --- src/modules/fare.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index 63ebdb2a..fe1a2965 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -38,15 +38,17 @@ const initDatabase = async () => { await acc; if (from._id === to._id) return; let tableFare = []; - const prevTaxiFare = await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - }, - { fare: true } - ) - .clone().fare; + const prevTaxiFare = ( + await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, + }, + { fare: true } + ) + .clone() + ).fare; const fare = prevTaxiFare ? prevTaxiFare : ( From ef3bc02a3f19c232aba58498525e116718601ff1 Mon Sep 17 00:00:00 2001 From: ybmin Date: Sat, 20 Jul 2024 01:35:13 +0900 Subject: [PATCH 30/33] Fix: code review --- app.js | 2 +- loadenv.js | 4 +- package.json | 1 - src/modules/fare.js | 258 ++++++++++++++++++--------- src/modules/stores/mongo.js | 4 +- src/routes/fare.js | 8 +- src/schedules/index.js | 5 +- src/schedules/updateMajorTaxiFare.js | 3 +- src/schedules/updateMinorTaxiFare.js | 2 +- src/services/fare.js | 125 +++---------- 10 files changed, 209 insertions(+), 203 deletions(-) diff --git a/app.js b/app.js index 37ae32c1..74035415 100644 --- a/app.js +++ b/app.js @@ -88,4 +88,4 @@ app.set("io", startSocketServer(serverHttp)); require("./src/schedules")(app); // [Module] 택시 예상 비용 db 초기화 -require("./src/modules/fare").initDatabase(); +require("./src/modules/fare").initializeDatabase(); diff --git a/loadenv.js b/loadenv.js index a1335db2..cbf47d88 100644 --- a/loadenv.js +++ b/loadenv.js @@ -45,7 +45,7 @@ module.exports = { }, eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional naverMap: { - naverMapApiId: process.env.NAVER_MAP_API_ID || false, // optional - naverMapApiKey: process.env.NAVER_MAP_API_KEY || false, //optional + apiId: process.env.NAVER_MAP_API_ID, // optional + apiKey: process.env.NAVER_MAP_API_KEY, //optional }, }; diff --git a/package.json b/package.json index ab15a691..4ea2a648 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "dependencies": { "@adminjs/express": "^5.1.0", "@adminjs/mongoose": "^3.0.3", - "@types/express": "^4.17.21", "adminjs": "^6.8.7", "aws-sdk": "^2.1386.0", "axios": "^0.27.2", diff --git a/src/modules/fare.js b/src/modules/fare.js index fe1a2965..a37671dd 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -5,14 +5,18 @@ const { naverMap } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("./stores/mongo"); const naverMapApi = { - "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, - "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, + "X-NCP-APIGW-API-KEY-ID": naverMap.apiId, + "X-NCP-APIGW-API-KEY": naverMap.apiKey, }; -const naverMapApiCall = - "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; +// 30분 간격으로 하루를 48개의 시간대로 나누어 택시 요금을 계산합니다. const timeConstants = 48; +/** + * 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) + * @param {Date} time: 시간 + * @returns {number} scaledTime + */ const scaledTime = (time) => { return ( timeConstants * time.getDay() + @@ -21,100 +25,184 @@ const scaledTime = (time) => { ); }; -const initDatabase = async () => { +/** + * 데이터베이스를 초기화합니다. 존재하지 않는 필드가 있을때, 기존의 값으로 초기화해 놓거나, 아얘 비어있을 경우에 api를 통해 값을 받아와 초기화합니다. + * @returns + */ +const initializeDatabase = async () => { try { if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false + naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined ) { logger.error( "There is no credential for Naver Map. Taxi Fare functions are disabled." ); return; } - const location = await locationModel.find({ isValid: { $eq: true } }); - location.map(async (from) => { - location.reduce(async (acc, to) => { - await acc; - if (from._id === to._id) return; - let tableFare = []; - const prevTaxiFare = ( - await taxiFareModel - .findOne( - { - from: from._id, - to: to._id, - }, - { fare: true } - ) - .clone() - ).fare; - const fare = prevTaxiFare - ? prevTaxiFare - : ( - await axios.get( - `${ - naverMapApiCall + from.longitude + "," + from.latitude - }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, - { headers: naverMapApi } - ) - ).data.route.traoptimal[0].summary.taxiFare; - if (from.koName === "카이스트 본원" && to.koName === "대전역") { - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - updateOne: { - filter: { from: from._id, to: to._id, time: i, isMajor: true }, - update: { - $setOnInsert: { - fare: fare, + const location = await locationModel + .find({ isValid: { $eq: true } }) + .lean(); + + await Promise.all( + location.map(async (from) => { + return Promise.all( + location.map(async (to) => { + if (from._id === to._id) return; + let tableFare = []; + const prevTaxiFare = ( + await taxiFareModel + .findOne( + { + from: from._id, + to: to._id, }, - }, - upsert: true, - }, - }); - }); - } else if (from.koName === "대전역" && to.koName === "카이스트 본원") { - [...Array(timeConstants * 7)].map((_, i) => { - tableFare.push({ - updateOne: { - filter: { from: from._id, to: to._id, time: i, isMajor: true }, - update: { - $setOnInsert: { - fare: fare, + { fare: true } + ) + .clone() + ).fare; + const fare = prevTaxiFare + ? prevTaxiFare + : await callTaxiFare(from, to); + if ( + (from.koName === "카이스트 본원" && to.koName === "대전역") || + (from.koName === "대전역" && to.koName === "카이스트 본원") + ) { + [...Array(timeConstants * 7)].map((_, i) => { + tableFare.push({ + updateOne: { + filter: { + from: from._id, + to: to._id, + time: i, + isMajor: true, + }, + update: { + $setOnInsert: { + fare: fare, + }, + }, + upsert: true, }, - }, - upsert: true, - }, - }); - }); - } else { - [...Array(7)].map((_, i) => { - tableFare.push({ - updateOne: { - filter: { - from: from._id, - to: to._id, - time: i * timeConstants, - isMajor: false, - }, - update: { - $setOnInsert: { - fare: fare, + }); + }); + } else { + [...Array(7)].map((_, i) => { + tableFare.push({ + updateOne: { + filter: { + from: from._id, + to: to._id, + time: i * timeConstants, + isMajor: false, + }, + update: { + $setOnInsert: { + fare: fare, + }, + }, + upsert: true, }, - }, - upsert: true, - }, - }); - }); - } - await taxiFareModel.bulkWrite(tableFare); - await new Promise((resolve) => setTimeout(resolve, 200)); - return acc; - }, Promise.resolve()); - }); + }); + }); + } + await taxiFareModel.bulkWrite(tableFare); + await new Promise((resolve) => setTimeout(resolve, 200)); + }) + ); + }) + ); } catch (err) { logger.error("Error occured while initializing database: " + err.message); } }; -module.exports = { scaledTime, initDatabase }; +/** + * 주어진 from, to, sTime에 대한 단일 택시 요금을 업데이트합니다. + * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. + * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. + * @param {number} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) + * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 경로 / 이외 경로 + */ +const updateTaxiFare = async (sTime, isMajor) => { + if ( + naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined + ) { + logger.error( + "There is no credential for Naver Map. Taxi Fare functions are disabled." + ); + return; + } + const prevFares = await taxiFareModel + .find({ + time: sTime, + isMajor: isMajor, + }) + .clone(); + await prevFares.reduce(async (acc, item) => { + const from = await locationModel.findOne({ _id: item.from }); + const to = await locationModel.findOne({ _id: item.to }); + + await acc; + await callTaxiFare + .catch((err) => { + logger.error(err.message); + }) + .then(async (fare) => { + if (fare) { + await taxiFareModel.updateOne( + { from: item.from, to: item.to, time: sTime }, + { fare: fare }, + (err, docs) => { + if (err) + logger.error( + "Error occured while updating Taxi Fare document: " + + err.message + ); + } + ); + } + }) + .catch((err) => { + logger.error(err.message); + }); + await new Promise((resolve) => setTimeout(() => resolve, 200)); + return acc; + }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 +}; + +/** + * @param {locationSchema} from : 출발지 (longitude, latitude) + * @param {locationSchema} to : 도착지 (longitude, latitude) + * @returns naver map api call을 통해 받아온 예상 택시 요금 + */ +const callTaxiFare = async (from, to) => { + if ( + naverMapApi["X-NCP-APIGW-API-KEY"] === undefined || + naverMapApi["X-NCP-APIGW-API-KEY-ID"] === undefined + ) { + logger.error( + "There is no credential for Naver Map. Taxi Fare functions are disabled." + ); + return; + } + return ( + await axios.get( + `${ + "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start=" + + from.longitude + + "," + + from.latitude + }&goal=${to.longitude + "," + to.latitude}&options=traoptimal`, + { headers: naverMapApi } + ) + ).data.route.traoptimal[0].summary.taxiFare; +}; + +module.exports = { + scaledTime, + initializeDatabase, + updateTaxiFare, + callTaxiFare, +}; diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index 6895e8be..8f837775 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -207,8 +207,8 @@ const adminLogSchema = Schema({ const taxiFareSchema = Schema( { - from: { type: Schema.Types.ObjectId, required: true }, // 출발지 - to: { type: Schema.Types.ObjectId, required: true }, // 도착지 + from: { type: Schema.Types.ObjectId, ref: "Location", required: true }, // 출발지 + to: { type: Schema.Types.ObjectId, ref: "Location", required: true }, // 도착지 isMajor: { type: Boolean, default: false }, // 카이스트 본원 <-> 대전역 경로 여부 time: { type: Number, required: true }, // 출발 시간 (24h를 30분 단위로 분리 & 요일 정보도 하나로 관리, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) fare: { type: Number, default: false }, // 예상 택시 요금 diff --git a/src/routes/fare.js b/src/routes/fare.js index d4c6e2aa..9455495a 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -2,10 +2,14 @@ const express = require("express"); const { validateQuery } = require("../middlewares/zod"); const { fareZod } = require("./docs/schemas/fareSchema"); -const { getTaxiFare } = require("../services/fare"); +const { getTaxiFareHandler } = require("../services/fare"); const router = express.Router(); -router.get("/getTaxiFare", validateQuery(fareZod.getTaxiFare), getTaxiFare); +router.get( + "/getTaxiFare", + validateQuery(fareZod.getTaxiFare), + getTaxiFareHandler +); module.exports = router; diff --git a/src/schedules/index.js b/src/schedules/index.js index 76eeac10..23fe121e 100644 --- a/src/schedules/index.js +++ b/src/schedules/index.js @@ -1,10 +1,11 @@ const cron = require("node-cron"); -const { naverMapApiId, naverMapApiKey } = require("../../loadenv").naverMap; +const { apiId: naverMapApiId, apiKey: naverMapApiKey } = + require("../../loadenv").naverMap; const registerSchedules = (app) => { cron.schedule("*/5 * * * *", require("./notifyBeforeDepart")(app)); cron.schedule("*/10 * * * *", require("./notifyAfterArrival")(app)); - if (naverMapApiId !== false && naverMapApiKey !== false) { + if (naverMapApiId !== undefined && naverMapApiKey !== undefined) { cron.schedule("0,30 * * * * ", require("./updateMajorTaxiFare")(app)); cron.schedule("0 18 * * *", require("./updateMinorTaxiFare")(app)); } diff --git a/src/schedules/updateMajorTaxiFare.js b/src/schedules/updateMajorTaxiFare.js index 57446525..2f0afa03 100644 --- a/src/schedules/updateMajorTaxiFare.js +++ b/src/schedules/updateMajorTaxiFare.js @@ -1,7 +1,6 @@ const logger = require("../modules/logger"); -const { updateTaxiFare } = require("../services/fare"); -const { scaledTime } = require("../modules/fare"); +const { scaledTime, updateTaxiFare } = require("../modules/fare"); /* 카이스트 본원<-> 대전역 경로에 대한 택시 요금을 매 30분간격(매시 0분과 30분)으로 1주일 단위 캐싱합니다. */ module.exports = (app) => async () => { diff --git a/src/schedules/updateMinorTaxiFare.js b/src/schedules/updateMinorTaxiFare.js index 52ba53f3..ef1dd241 100644 --- a/src/schedules/updateMinorTaxiFare.js +++ b/src/schedules/updateMinorTaxiFare.js @@ -1,6 +1,6 @@ const logger = require("../modules/logger"); -const { updateTaxiFare } = require("../services/fare"); +const { updateTaxiFare } = require("../modules/fare"); /* 카이스트 본원<-> 대전역 경로 외의 238개 경로에 대한 택시 요금을 매일 18:00시에 1주일 단위로 캐싱합니다. */ module.exports = (app) => async () => { diff --git a/src/services/fare.js b/src/services/fare.js index b6fb10a1..9ad6510f 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -1,17 +1,13 @@ -const axios = require("axios"); const logger = require("../modules/logger"); const { naverMap } = require("../../loadenv"); const { taxiFareModel, locationModel } = require("../modules/stores/mongo"); -const { scaledTime } = require("../modules/fare"); +const { scaledTime, callTaxiFare } = require("../modules/fare"); -// Naver Cloud Platform Maps Directions 5 API Keys const naverMapApi = { - "X-NCP-APIGW-API-KEY-ID": naverMap.naverMapApiId, - "X-NCP-APIGW-API-KEY": naverMap.naverMapApiKey, + "X-NCP-APIGW-API-KEY-ID": naverMap.apiId, + "X-NCP-APIGW-API-KEY": naverMap.apiKey, }; -const naverMapApiCall = - "https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving?start="; /** * 주어진 from, to, time에 대한 택시 요금을 반환합니다. @@ -22,29 +18,27 @@ const naverMapApiCall = * - @param {mongoose.Schema.Types.ObjectId} to - 도착지 * - @param {Date} time - 출발 시간 (ISO 8601) */ -const getTaxiFare = async (req, res) => { +const getTaxiFareHandler = async (req, res) => { try { if ( naverMapApi["X-NCP-APIGW-API-KEY"] === false || naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false ) { res.status(503).json({ - error: "fare/getTaxiFare: Naver Map API credential not found", + error: "fare/getTaxiFareHandler: Naver Map API credential not found", }); return; } - const from = await locationModel - .findOne({ - _id: { $eq: req.query.from }, - }) - .clone(); - const to = await locationModel - .findOne({ _id: { $eq: req.query.to } }) - .clone(); + const from = await locationModel.findOne({ + _id: { $eq: req.query.from }, + }); + const to = await locationModel.findOne({ _id: { $eq: req.query.to } }); const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { - res.status(400).json({ error: "fare/getTaxiFare: Wrong location" }); + res + .status(400) + .json({ error: "fare/getTaxiFareHandler: Wrong location" }); return; } const isMajor = ( @@ -82,17 +76,9 @@ const getTaxiFare = async (req, res) => { .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + await callTaxiFare(from, to) + .then((fare) => { + res.status(200).json({ fare: fare }); }) .catch((err) => { logger.error(err.message); @@ -119,17 +105,9 @@ const getTaxiFare = async (req, res) => { .clone(); //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare <= 0) { - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .then((text) => { - res - .status(200) - .json({ fare: text.data.route.traoptimal[0].summary.taxiFare }); + await callTaxiFare(from, to) + .then((fare) => { + res.status(200).json({ fare: fare }); }) .catch((err) => { logger.error(err.message); @@ -142,71 +120,8 @@ const getTaxiFare = async (req, res) => { logger.error(err.message); res .status(500) - .json({ error: "fare/getTaxiFare: Failed to load Taxi Fare" }); + .json({ error: "fare/getTaxiFareHandler: Failed to load Taxi Fare" }); } }; -/** - * 주어진 from, to, sTime에 대한 단일 택시 요금을 업데이트합니다. - * @summary 카이스트 본원 <-> 대전역의 경로를 제외한 다른 경로의 경우, cron에 의해 매일 18:00시의 택시 요금을 업데이트 하게 됩니다. - * @summary 카이스트 본원 <-> 대전역의 경우, 미리 캐싱해놓은 데이터를 기반으로 주어진 시간(30분 간격)에 대한 택시 요금을 반환합니다. - * @param {number} sTime - 출발 시간 (scaledTime에 의해 변경된 시간, 0 ~ 6 (Sunday~Saturday) * 48 + 0 ~ 47 (0:00 ~ 23:30)) - * @param {Boolean} isMajor - 카이스트 본원 <-> 대전역 경로 / 이외 경로 - */ -const updateTaxiFare = async (sTime, isMajor) => { - if ( - naverMapApi["X-NCP-APIGW-API-KEY"] === false || - naverMapApi["X-NCP-APIGW-API-KEY-ID"] === false - ) { - logger.error( - "There is no credential for Naver Map. Taxi Fare functions are disabled." - ); - return; - } - const prevFares = await taxiFareModel - .find({ - time: sTime, - isMajor: isMajor, - }) - .clone(); - await prevFares.reduce(async (acc, item) => { - const from = await locationModel.findOne({ _id: item.from }).clone(); - const to = await locationModel.findOne({ _id: item.to }).clone(); - - await acc; - await axios - .get( - `${naverMapApiCall + from.longitude + "," + from.latitude}&goal=${ - to.longitude + "," + to.latitude - }&options=traoptimal`, - { headers: naverMapApi } - ) - .catch((err) => { - logger.error(err.message); - }) - .then(async (res) => { - if (res && res.data) { - await taxiFareModel - .updateOne( - { from: item.from, to: item.to, time: sTime }, - { fare: res.data.route.traoptimal[0].summary.taxiFare }, - (err, docs) => { - if (err) - logger.error( - "Error occured while updating Taxi Fare document: " + - err.message - ); - } - ) - .clone(); - } - }) - .catch((err) => { - logger.error(err.message); - }); - await new Promise((resolve) => setTimeout(() => resolve, 200)); - return acc; - }, Promise.resolve()); // 초기값 설정 안 하면, 처음에 acc가 undefined로 들어가서 첫 인덱스를 to에서 못 쓰게 됨 -}; - -module.exports = { getTaxiFare, updateTaxiFare }; +module.exports = { getTaxiFareHandler }; From ad541726324632f15337106b2f4665339417dce1 Mon Sep 17 00:00:00 2001 From: ybmin Date: Sat, 20 Jul 2024 01:38:24 +0900 Subject: [PATCH 31/33] Remove: unused library --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de50fb41..db4e122d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ dependencies: '@adminjs/mongoose': specifier: ^3.0.3 version: 3.0.3(adminjs@6.8.7)(mongoose@6.12.0) - '@types/express': - specifier: ^4.17.21 - version: 4.17.21 adminjs: specifier: ^6.8.7 version: 6.8.7 From 81a37dae8775c5cabae24af60c02f434be0939b8 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 20 Jul 2024 10:31:49 +0900 Subject: [PATCH 32/33] Fix: taxiFareModel is not displayed in admin page --- src/routes/admin.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/admin.js b/src/routes/admin.js index 7cd28735..e7748523 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -13,6 +13,7 @@ const { adminLogModel, deviceTokenModel, notificationOptionModel, + taxiFareModel, } = require("../modules/stores/mongo"); const { buildResource } = require("../modules/adminResource"); @@ -36,6 +37,7 @@ const resources = [ adminLogModel, deviceTokenModel, notificationOptionModel, + taxiFareModel, ] .map(buildResource()) .concat(require("../lottery").resources); From 7c48579bed6d4f2eaf34df2317368825dbd200e3 Mon Sep 17 00:00:00 2001 From: ybmin Date: Sun, 21 Jul 2024 22:00:48 +0900 Subject: [PATCH 33/33] Fix: getTaxiFare -> getTaxiFareHandler --- src/modules/fare.js | 4 ++-- src/routes/docs/fare.js | 4 ++-- src/routes/docs/schemas/fareSchema.js | 2 +- src/routes/fare.js | 2 +- src/services/fare.js | 22 +++++++++++++--------- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/modules/fare.js b/src/modules/fare.js index a37671dd..b2da72a5 100644 --- a/src/modules/fare.js +++ b/src/modules/fare.js @@ -59,7 +59,7 @@ const initializeDatabase = async () => { }, { fare: true } ) - .clone() + .lean() ).fare; const fare = prevTaxiFare ? prevTaxiFare @@ -139,7 +139,7 @@ const updateTaxiFare = async (sTime, isMajor) => { time: sTime, isMajor: isMajor, }) - .clone(); + .lean(); await prevFares.reduce(async (acc, item) => { const from = await locationModel.findOne({ _id: item.from }); const to = await locationModel.findOne({ _id: item.to }); diff --git a/src/routes/docs/fare.js b/src/routes/docs/fare.js index 72707cf1..62fbfa50 100644 --- a/src/routes/docs/fare.js +++ b/src/routes/docs/fare.js @@ -40,10 +40,10 @@ fareDocs[`${apiPrefix}/getTaxiFare`] = { }, }, 500: { - description: "fare/getTaxiFare: Failed to load taxi fare", + description: "fare/getTaxiFareHandler: Failed to load taxi fare", content: { "text/html": { - example: "fare/getTaxiFare: Failed to load taxi fare", + example: "fare/getTaxiFareHandler: Failed to load taxi fare", }, }, }, diff --git a/src/routes/docs/schemas/fareSchema.js b/src/routes/docs/schemas/fareSchema.js index 27f63450..0812a86a 100644 --- a/src/routes/docs/schemas/fareSchema.js +++ b/src/routes/docs/schemas/fareSchema.js @@ -3,7 +3,7 @@ const { zodToSchemaObject } = require("../utils"); const { objectId } = require("../../../modules/patterns"); const fareZod = { - getTaxiFare: z.object({ + getTaxiFareHandler: z.object({ from: z.string().regex(objectId), to: z.string().regex(objectId), time: z.string().datetime(), diff --git a/src/routes/fare.js b/src/routes/fare.js index 9455495a..0cbe37c3 100644 --- a/src/routes/fare.js +++ b/src/routes/fare.js @@ -8,7 +8,7 @@ const router = express.Router(); router.get( "/getTaxiFare", - validateQuery(fareZod.getTaxiFare), + validateQuery(fareZod.getTaxiFareHandler), getTaxiFareHandler ); diff --git a/src/services/fare.js b/src/services/fare.js index 9ad6510f..55d1ad33 100644 --- a/src/services/fare.js +++ b/src/services/fare.js @@ -29,10 +29,14 @@ const getTaxiFareHandler = async (req, res) => { }); return; } - const from = await locationModel.findOne({ - _id: { $eq: req.query.from }, - }); - const to = await locationModel.findOne({ _id: { $eq: req.query.to } }); + const from = await locationModel + .findOne({ + _id: { $eq: req.query.from }, + }) + .lean(); + const to = await locationModel + .findOne({ _id: { $eq: req.query.to } }) + .lean(); const sTime = scaledTime(new Date(req.query.time)); if (!from || !to) { @@ -54,7 +58,7 @@ const getTaxiFareHandler = async (req, res) => { ); } ) - .clone() + .lean() ).isMajor; // 시간대별 정보 관리 (현재: 카이스트 본원 <-> 대전역) if (isMajor) { @@ -73,8 +77,8 @@ const getTaxiFareHandler = async (req, res) => { ); } ) - .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + .lean(); + //만일 초기화 되지 않은 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare <= 0) { await callTaxiFare(from, to) .then((fare) => { @@ -102,8 +106,8 @@ const getTaxiFareHandler = async (req, res) => { ); } ) - .clone(); - //만일 cron이 아직 돌지 않은 상태의 시간대의 정보를 필요로하는 비상시의 경우 대비 + .lean(); + //만일 초기화 되지 않은 시간대의 정보를 필요로하는 비상시의 경우 대비 if (!taxiFare || taxiFare.fare <= 0) { await callTaxiFare(from, to) .then((fare) => {