Skip to content

Commit 9a28699

Browse files
authored
Merge pull request #27 from CheatSOL/develop
2주차 Develop 브랜치 main으로 머지
2 parents 9e654a4 + 3a1add1 commit 9a28699

26 files changed

+907
-87
lines changed

.github/workflows/main.yml

+7
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,15 @@ jobs:
7070
sudo npm install -g pm2
7171
# Navigate to the application directory
7272
cd /home/ec2-user/CheatSOL-BE
73+
74+
# Install Python and pip
75+
sudo yum install -y python3
76+
sudo alternatives --set python /usr/bin/python3
77+
sudo yum install -y python3-pip
78+
7379
# Install project dependencies
7480
npm install --production
81+
pip install -r requirements.txt
7582
# Restart or start the application with pm2
7683
pm2 stop all
7784
pm2 delete all

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@ dist
130130
.pnp.*
131131

132132
package-lock.json
133+
.idea/

app.js

+35-8
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,43 @@ var express = require("express");
33
var path = require("path");
44
var cookieParser = require("cookie-parser");
55
var logger = require("morgan");
6-
6+
const {wsdata}=require("./src/utils/WSPrice");
77
var indexRouter = require("./src/routes/index");
8-
var usersRouter = require("./src/routes/users");
8+
var companyRouter = require("./src/routes/company");
9+
var googleRouter = require("./src/routes/google");
10+
var stockInfoRouter = require("./src/routes/stock.info.detail");
911
const db = require("./src/models/DB");
10-
12+
const http=require('http');
1113
var app = express();
12-
1314
app.use(logger("dev"));
1415
app.use(express.json());
1516
app.use(express.urlencoded({ extended: false }));
1617
app.use(cookieParser());
18+
const server = http.createServer(app);
19+
const WebSocket = require('ws');
20+
const wss = new WebSocket.Server({ server });
21+
wss.on('connection', async function connection(ws) {
22+
console.log('새로운 WebSocket 클라이언트가 연결되었습니다.');
23+
let id;
24+
ws.on('message', (message) => {
25+
const data = JSON.parse(message);
26+
console.log('Received data:', data);
27+
id = data.id;
28+
if (id) {
29+
wsdata(ws, id); // id가 설정된 후에 wsdata 호출
30+
} else {
31+
console.error('ID is undefined');
32+
}
33+
});
34+
35+
ws.on('close', function close() {
36+
console.log('WebSocket 연결이 종료되었습니다.');
37+
});
38+
});
39+
server.listen(3002, () => {
40+
console.log('서버가 3002번 포트에서 실행 중입니다.');
41+
});
1742

18-
// 데이터베이스 연결 확인 및 동기화
1943
db.sequelize
2044
.authenticate()
2145
.then(() => {
@@ -32,11 +56,14 @@ db.sequelize
3256
});
3357

3458
app.use("/api", indexRouter);
35-
app.use("/users", usersRouter);
59+
app.use("/api/company", companyRouter);
60+
app.use("/api/trends", googleRouter);
61+
app.use("/api/stockInfo", stockInfoRouter);
3662

3763
// catch 404 and forward to error handler
3864
app.use(function (req, res, next) {
39-
next(createError(404));
65+
createError(404);
66+
res.json({ code: 404, message: "서버에 url과 일치하는 api가 없습니다." });
4067
});
4168

4269
// error handler
@@ -47,7 +74,7 @@ app.use(function (err, req, res, next) {
4774

4875
// render the error page
4976
res.status(err.status || 500);
50-
res.render("error");
77+
res.json(res.locals);
5178
});
5279

5380
module.exports = app;

package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,22 @@
88
},
99
"dependencies": {
1010
"axios": "^1.7.2",
11+
"body-parser": "^1.20.2",
12+
"cheerio": "^1.0.0-rc.12",
1113
"cookie-parser": "~1.4.4",
1214
"debug": "~2.6.9",
1315
"dotenv": "^16.4.5",
1416
"ejs": "~2.6.1",
1517
"express": "~4.16.1",
18+
"google-trends-api": "^4.9.2",
1619
"http-errors": "~1.6.3",
20+
"https": "^1.0.0",
1721
"morgan": "~1.9.1",
22+
"nodemon": "^3.1.3",
1823
"pg": "^8.12.0",
1924
"pg-hstore": "^2.3.4",
20-
"sequelize": "^6.37.3"
25+
"python-shell": "^5.0.0",
26+
"sequelize": "^6.37.3",
27+
"ws": "^8.17.1"
2128
}
2229
}

requirements.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
psycopg2-binary==2.9.9
2+
gensim==4.3.2
3+
pandas==2.2.2
4+
psycopg2==2.9.9
5+
python-dotenv==1.0.1
6+
scipy==1.10.0

src/controllers/AuthController.js

+43-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,48 @@
1-
const AuthToken = require("../models/AuthToken");
1+
const db = require("../models/DB");
22

3-
async function getAuthToken(ApiHost) {
3+
async function getAuthToken(apiHost) {
44
try {
5-
const authTokens = await AuthToken.find(ApiHost);
6-
authTokens.forEach((token) => console.log(token.toJSON()));
5+
const authToken = await db.AuthToken.findOne({
6+
where: { name: apiHost },
7+
order: [['updatedAt', 'DESC']]
8+
});
9+
10+
return authToken.dataValues;
711
} catch (error) {
8-
console.error("Error fetching AuthTokens:", error);
12+
return null;
913
}
1014
}
15+
16+
async function setAuthToken(
17+
apiHost,
18+
accessToken,
19+
tokenExpired,
20+
tokenType = "Bearer"
21+
) {
22+
await db.AuthToken.create({
23+
name: apiHost,
24+
access_token: accessToken,
25+
access_token_expired: tokenExpired,
26+
token_type: tokenType,
27+
});
28+
}
29+
30+
async function updateAuthToken(
31+
apiHost,
32+
accessToken,
33+
tokenExpired,
34+
tokenType = "Bearer"
35+
) {
36+
await db.AuthToken.update(
37+
{
38+
access_token: accessToken,
39+
access_token_expired: tokenExpired,
40+
token_type: tokenType,
41+
},
42+
{
43+
where: { name: apiHost },
44+
}
45+
);
46+
}
47+
48+
module.exports = { getAuthToken, setAuthToken, updateAuthToken };

src/controllers/CompanyController.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const db = require("../models/DB");
2+
3+
async function getCompanyByCode(code) {
4+
try {
5+
const company = await db.Company.findOne({ where: { code: code } });
6+
console.log(company);
7+
return company.dataValues;
8+
} catch (error) {
9+
console.log("회사 정보가 없음");
10+
return null;
11+
}
12+
}
13+
14+
async function getCompanies() {
15+
try {
16+
const companies = await db.Company.findAll();
17+
return companies;
18+
} catch (error) {
19+
console.log("회사 정보가 없음");
20+
return null;
21+
}
22+
}
23+
24+
async function setCompanies(companies) {
25+
await db.Company.destroy({ where: {} });
26+
const companyData = companies.map((company) => ({
27+
name: company.name,
28+
code: company.code,
29+
}));
30+
31+
console.log(companyData);
32+
await db.Company.bulkCreate(companyData);
33+
}
34+
35+
module.exports = { setCompanies, getCompanies, getCompanyByCode };

src/controllers/NewsCrawling.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const axios=require('axios');
2+
const cheerio=require('cheerio');
3+
const { Model, Op } = require('sequelize');
4+
const { CompanyNews, Company } = require('../models/DB');
5+
const fetchNews = require('../utils/NaverStockNews');
6+
7+
async function fetchNewsContent(link) {
8+
try {
9+
const response = await axios.get(link);
10+
const $ = cheerio.load(response.data);
11+
const content =
12+
$("#newsct_article").text().trim() ||
13+
$("._article_content").text().trim();
14+
return content;
15+
} catch (error) {
16+
console.error(`Error fetching news content from ${link}:`, error);
17+
return null;
18+
}
19+
}
20+
21+
async function saveCodes() {
22+
const companies = await Company.findAll();
23+
const companyCodes = companies.map((company) => ({
24+
code: company.dataValues.code,
25+
id: company.dataValues.id,
26+
}));
27+
return companyCodes;
28+
}
29+
function delay(ms) {
30+
return new Promise((resolve) => setTimeout(resolve, ms));
31+
}
32+
async function saveNewsToDatabase(id, newsItems) {
33+
const newsData = [];
34+
35+
//console.log("newsItems", newsItems);
36+
for (let items of newsItems) {
37+
for (let item of items) {
38+
const link = `https://n.news.naver.com/article/${item.officeId}/${item.articleId}`;
39+
40+
// 1초 지연 (1000ms)
41+
await delay(100);
42+
43+
try {
44+
const detailedContent = await fetchNewsContent(link);
45+
46+
newsData.push({
47+
title: item.title,
48+
content: detailedContent || item.body,
49+
link: link,
50+
company_id: id,
51+
});
52+
} catch (error) {
53+
console.error(`Error fetching news content for ${link}:`, error);
54+
// 에러 처리 (예: 특정 뉴스 아이템에 대한 실패를 로그로 남기거나, 다시 시도할 수 있도록 로직 추가)
55+
}
56+
}
57+
}
58+
// 데이터베이스에 저장
59+
if (newsData.length > 0) {
60+
await CompanyNews.bulkCreate(newsData);
61+
}
62+
}
63+
64+
// Express 컨트롤러 함수
65+
async function handleNewsDatas(data, res) {
66+
try {
67+
// fetchNews 함수로 뉴스 아이템들 가져오기
68+
const newsItems = await fetchNews(data.code);
69+
70+
// 가져온 뉴스 아이템들을 데이터베이스에 저장하기
71+
await saveNewsToDatabase(data.id, newsItems);
72+
} catch (error) {
73+
console.error(`Error handling news data for company ${data.id}:`, error);
74+
// 에러 처리 (예: 특정 회사에 대한 실패를 로그로 남기거나, 다시 시도할 수 있도록 로직 추가)
75+
}
76+
}
77+
78+
const handleCompanyNews = async (req, res, next) => {
79+
try {
80+
// 회사 코드를 데이터베이스에서 가져오기
81+
const companyCodes = await saveCodes();
82+
83+
// 각 회사 코드에 대해 뉴스 아이템을 가져오고 데이터베이스에 저장하기
84+
for (const data of companyCodes) {
85+
await handleNewsDatas(data, res);
86+
}
87+
88+
res
89+
.status(200)
90+
.json({ message: `Successfully fetched and saved news items.` });
91+
} catch (error) {
92+
console.error("Error handling news data:", error);
93+
// 에러 응답 보내기
94+
res.status(500).json({ error: "Failed to fetch and save news items." });
95+
}
96+
};
97+
98+
module.exports = handleCompanyNews;

src/controllers/Word2VecController.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { PythonShell } = require("python-shell");
2+
3+
require("dotenv").config(); // dotenv 설정 불러오기
4+
5+
async function runPythonScript(scriptPath, options) {
6+
console.log("Running Python Script..."); // Initial log
7+
return new Promise((resolve, reject) => {
8+
PythonShell.run(scriptPath, options)
9+
.then((results) => {
10+
try {
11+
const parsedResults = JSON.parse(results.join(""));
12+
resolve(parsedResults);
13+
} catch (parseError) {
14+
reject(parseError);
15+
}
16+
})
17+
.catch((err) => {
18+
console.error(err);
19+
});
20+
});
21+
}
22+
23+
async function getSimilarityCompanies(word) {
24+
console.log("word", word);
25+
try {
26+
const options = {
27+
mode: "text",
28+
pythonOptions: ["-u"],
29+
scriptPath: "src/scripts",
30+
args: [word],
31+
env: process.env,
32+
};
33+
const results = await runPythonScript("get_similar_words.py", options);
34+
35+
return results;
36+
} catch (error) {
37+
throw new Error(error);
38+
}
39+
}
40+
41+
module.exports = { getSimilarityCompanies };

0 commit comments

Comments
 (0)