From d21f2c15a25a2ed11ee72932389a9f9f9782f8e4 Mon Sep 17 00:00:00 2001 From: jiminseon <20201020@dongduk.ac.kr> Date: Fri, 11 Jul 2025 16:40:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[feat]=20=EB=B3=B4=EC=9C=A0=20=EC=A3=BC?= =?UTF-8?q?=EC=8B=9D=20=EC=A1=B0=ED=9A=8C=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 9 ++++++ package-lock.json | 59 +++++++++++++++++++++++++++++++++++++++- package.json | 4 ++- routes/real.js | 18 ++++++++++++ services/stockService.js | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 routes/real.js create mode 100644 services/stockService.js diff --git a/app.js b/app.js index 9255653..fb37792 100644 --- a/app.js +++ b/app.js @@ -1,3 +1,4 @@ +require("dotenv").config(); var createError = require("http-errors"); var express = require("express"); var path = require("path"); @@ -6,6 +7,7 @@ var logger = require("morgan"); var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); +var balanceRouter = require("./routes/real"); var app = express(); @@ -17,6 +19,7 @@ app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/users", usersRouter); +app.use("/api/real", balanceRouter); // catch 404 and forward to error handler app.use(function (req, res, next) { @@ -34,4 +37,10 @@ app.use(function (err, req, res, next) { res.render("error"); }); +const PORT = process.env.PORT || 3001; + +app.listen(PORT, () => { + console.log(`๐Ÿš€ ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘: http://localhost:${PORT}`); +}); + module.exports = app; diff --git a/package-lock.json b/package-lock.json index 830f843..d980021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,13 +7,16 @@ "": { "name": "upanddown-server", "version": "0.0.0", + "license": "ISC", "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", + "dotenv": "^17.2.0", "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", - "morgan": "~1.9.1" + "morgan": "~1.9.1", + "node-fetch": "^2.7.0" } }, "node_modules/accepts": { @@ -147,6 +150,18 @@ "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", "license": "MIT" }, + "node_modules/dotenv": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", + "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -399,6 +414,26 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -547,6 +582,12 @@ "node": ">= 0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -586,6 +627,22 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } } } } diff --git a/package.json b/package.json index 8149758..3687952 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ "dependencies": { "cookie-parser": "~1.4.4", "debug": "~2.6.9", + "dotenv": "^17.2.0", "ejs": "~2.6.1", "express": "~4.16.1", "http-errors": "~1.6.3", - "morgan": "~1.9.1" + "morgan": "~1.9.1", + "node-fetch": "^2.7.0" } } diff --git a/routes/real.js b/routes/real.js new file mode 100644 index 0000000..ca18d19 --- /dev/null +++ b/routes/real.js @@ -0,0 +1,18 @@ +const express = require("express"); +const router = express.Router(); +const { getBalance } = require("../services/stockService"); + +router.get("/", async (req, res) => { + const cano = "50143725"; + const acnt = "01"; + + try { + const data = await getBalance(cano, acnt); + res.json(data); + } catch (err) { + console.error("์ž”๊ณ  ์กฐํšŒ ์˜ค๋ฅ˜:", err.message); + res.status(500).json({ error: "์ž”๊ณ  ์กฐํšŒ ์‹คํŒจ" }); + } +}); + +module.exports = router; diff --git a/services/stockService.js b/services/stockService.js new file mode 100644 index 0000000..ceb5b3a --- /dev/null +++ b/services/stockService.js @@ -0,0 +1,50 @@ +const fetch = require("node-fetch"); + +async function getBalance(cano, acntPrdtCd) { + try { + const url = new URL( + "/uapi/domestic-stock/v1/trading/inquire-balance", + process.env.API_DOMAIN + ); + + url.searchParams.set("CANO", cano); + url.searchParams.set("ACNT_PRDT_CD", acntPrdtCd); + url.searchParams.set("AFHR_FLPR_YN", "N"); + url.searchParams.set("INQR_DVSN", "02"); + url.searchParams.set("UNPR_DVSN", "01"); + url.searchParams.set("FUND_STTL_ICLD_YN", "N"); + url.searchParams.set("FNCG_AMT_AUTO_RDPT_YN", "N"); + url.searchParams.set("PRCS_DVSN", "00"); + url.searchParams.set("OFL_YN", ""); + url.searchParams.set("CTX_AREA_FK100", ""); + url.searchParams.set("CTX_AREA_NK100", ""); + + console.log("์š”์ฒญ URL:", url.toString()); + + const res = await fetch(url.toString(), { + method: "GET", + headers: { + authorization: `Bearer ${process.env.API_TOKEN}`, + appkey: process.env.API_APPKEY, + appsecret: process.env.API_APPSECRET, + tr_id: "VTTC8434R", + accept: "*/*", + "accept-encoding": "gzip, deflate, br", + connection: "keep-alive", + "user-agent": "PostmanRuntime/7.44.1", + }, + }); + + if (!res.ok) { + const errText = await res.text(); + throw new Error(`์ž”๊ณ  ์กฐํšŒ ์‹คํŒจ: ${res.status} - ${errText}`); + } + + return await res.json(); + } catch (err) { + console.error("๐Ÿšจ getBalance ์˜ค๋ฅ˜:", err.message); + throw err; + } +} + +module.exports = { getBalance }; From 0e122ee1f6e9904910e42cf66c232267c745916c Mon Sep 17 00:00:00 2001 From: jiminseon <20201020@dongduk.ac.kr> Date: Mon, 14 Jul 2025 09:06:11 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20token=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/real.js | 39 +++++++++++++++++++++++++++++++++++++++ services/stockService.js | 5 ++++- services/tokenService.js | 25 +++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 services/tokenService.js diff --git a/routes/real.js b/routes/real.js index ca18d19..04cae30 100644 --- a/routes/real.js +++ b/routes/real.js @@ -16,3 +16,42 @@ router.get("/", async (req, res) => { }); module.exports = router; + +// const express = require("express"); +// const router = express.Router(); +// const { getBalance } = require("../services/getBalance"); +// const Stock = require("../models/Stock"); +// const UserStock = require("../models/UserStock"); + +// router.post("/", async (req, res) => { +// try { +// const { userId, cano, acntPrdtCd } = req.body; +// const data = await getBalance(cano, acntPrdtCd); +// const stocks = data.output1; + +// for (const item of stocks) { +// const { pdno, prdt_name } = item; + +// // ์ฃผ์‹ ์ €์žฅ (์ค‘๋ณต ์ฒ˜๋ฆฌ) +// await Stock.updateOne( +// { stock_code: pdno }, +// { $set: { company: prdt_name, state: true } }, +// { upsert: true } +// ); + +// // ๋ณด์œ  ์ฃผ์‹ ์ €์žฅ +// await UserStock.updateOne( +// { user_id: userId, stock_code: pdno }, +// { $setOnInsert: { cumulative_score: 0 } }, +// { upsert: true } +// ); +// } + +// res.json({ count: stocks.length, stocks: stocks.map((s) => s.prdt_name) }); +// } catch (err) { +// console.error("์ž”๊ณ  ์กฐํšŒ ์˜ค๋ฅ˜:", err.message); +// res.status(500).json({ message: err.message }); +// } +// }); + +// module.exports = router; diff --git a/services/stockService.js b/services/stockService.js index ceb5b3a..fc26141 100644 --- a/services/stockService.js +++ b/services/stockService.js @@ -1,7 +1,10 @@ const fetch = require("node-fetch"); +const { getAccessToken } = require("./tokenService"); async function getBalance(cano, acntPrdtCd) { try { + const token = await getAccessToken(); + const url = new URL( "/uapi/domestic-stock/v1/trading/inquire-balance", process.env.API_DOMAIN @@ -24,7 +27,7 @@ async function getBalance(cano, acntPrdtCd) { const res = await fetch(url.toString(), { method: "GET", headers: { - authorization: `Bearer ${process.env.API_TOKEN}`, + authorization: `Bearer ${token}`, appkey: process.env.API_APPKEY, appsecret: process.env.API_APPSECRET, tr_id: "VTTC8434R", diff --git a/services/tokenService.js b/services/tokenService.js new file mode 100644 index 0000000..76cf6ea --- /dev/null +++ b/services/tokenService.js @@ -0,0 +1,25 @@ +const fetch = require("node-fetch"); + +let cachedToken = null; + +async function getAccessToken() { + if (cachedToken) return cachedToken; + + const res = await fetch(`${process.env.TOKEN_DOMAIN}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + grant_type: "client_credentials", + appkey: process.env.API_APPKEY, + appsecret: process.env.API_APPSECRET, + }), + }); + + if (!res.ok) throw new Error("ํ† ํฐ ๋ฐœ๊ธ‰ ์‹คํŒจ"); + + const data = await res.json(); + cachedToken = data.access_token; + return cachedToken; +} + +module.exports = { getAccessToken }; From edc83b0841409b1fa074a14ee882f4b8f1ff274b Mon Sep 17 00:00:00 2001 From: jiminseon <20201020@dongduk.ac.kr> Date: Mon, 14 Jul 2025 09:07:55 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[feat]=20cors=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 11 +++++++++++ package-lock.json | 23 +++++++++++++++++++++++ package.json | 1 + 3 files changed, 35 insertions(+) diff --git a/app.js b/app.js index fb37792..5960ab1 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,17 @@ var usersRouter = require("./routes/users"); var balanceRouter = require("./routes/real"); var app = express(); +const cors = require("cors"); + +/* CORS */ +const allowedOrigins = ["http://localhost:3000"]; + +app.use( + cors({ + origin: allowedOrigins, + credentials: true, + }) +); app.use(logger("dev")); app.use(express.json()); diff --git a/package-lock.json b/package-lock.json index d980021..b2e40b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "debug": "~2.6.9", "dotenv": "^17.2.0", "ejs": "~2.6.1", @@ -126,6 +127,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -434,6 +448,15 @@ } } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", diff --git a/package.json b/package.json index 3687952..ccaf886 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "homepage": "https://github.com/InserToken/UpAndDown-Server#readme", "dependencies": { "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "debug": "~2.6.9", "dotenv": "^17.2.0", "ejs": "~2.6.1",