redbean096@khu.ac.kr

pull origin master

......@@ -15,11 +15,11 @@ http://khuhub.khu.ac.kr/MotherProject/Jaksimsamil
> 작심삼일 알고리즘 문제풀이 도우미 서비스<br/>
>
> > 알고리즘 문제 풀이 스터디를 꾸준히 할 수 있게 돕는 웹 서비스입니다.
> > <br> [링크](http://ossfinal.com)에서 직접 사용해 보세요!
> > <br> [링크](http://www.ossjaksimsamil.ml:3000)에서 직접 사용해 보세요!
![그림1](https://user-images.githubusercontent.com/16442978/85690047-236d1d00-b70e-11ea-8d2b-480593c0daf3.png)
![KakaoTalk_20201209_203849946](/uploads/e3e7aea64c555c9542fef7ee2426cd68/KakaoTalk_20201209_203849946.png)
![그림2](https://user-images.githubusercontent.com/16442978/85690058-2536e080-b70e-11ea-98cd-45fdf04084ce.png)
![KakaoTalk_20201209_203825860](/uploads/809796e9fe3274b7f3643629c9d3b0e9/KakaoTalk_20201209_203825860.png)
# 기존 프로젝트의 기능
......@@ -40,7 +40,7 @@ http://khuhub.khu.ac.kr/MotherProject/Jaksimsamil
- 개선된 문제 추천 (사용자 실력 맞춤형)
# 기존의 마더 프로젝트에 새롭게 추가한 기능
-카카오 api를 이용한 문제추천 알림
-line 챗봇을 이용한 문제추천 알림
## Usages
......@@ -62,7 +62,7 @@ http://khuhub.khu.ac.kr/MotherProject/Jaksimsamil
1. Clone
```
git clone http://khuhub.khu.ac.kr/2020-2_open_source_sw_development_Han/JaksimSamil_Develop.git
git clone http://khuhub.khu.ac.kr/2020-2_open_source_sw_development_Han/Jaksimsamil.git
```
2. Install MongoDB(Ubuntu)
......@@ -89,7 +89,6 @@ touch .env
SERVER_PORT= ###
MONGO_URL= ###
JWT_SECRET= ###
KAKAO_JS_KEY = ###
```
4. Start Node Server
......
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
......@@ -3,25 +3,33 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.2",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.5.0",
"axios": "^0.21.0",
"immer": "^8.0.0",
"@material-ui/core": "^4.10.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"axios": "^0.19.2",
"eslint": "^7.14.0",
"immer": "^7.0.5",
"include-media": "^1.4.9",
"moment": "^2.27.0",
"moment": "^2.29.1",
"open-color": "^1.7.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-scripts": "4.0.1",
"redux": "^4.0.5",
"redux-actions": "^2.6.5",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^1.1.3",
"styled-components": "^5.1.1"
"styled-components": "^5.2.1"
},
"scripts": {
"start": "react-scripts start",
......@@ -30,7 +38,10 @@
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
"extends": "react-app",
"rules": {
"jsx-a11y/anchor-is-valid": "off"
}
},
"browserslist": {
"production": [
......@@ -44,6 +55,5 @@
"last 1 safari version"
]
},
"proxy": "http://localhost:4000",
"secure" : false
"proxy": "http://localhost:4000"
}
......
......@@ -25,6 +25,10 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<script src ="https://developers.kakao.com/sdk/js/kakao.min.js"></script>
<script type = "text/javascript">
Kakao.init("9454fd9d91effc97b064c201f04586d2");
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
......
......@@ -3,7 +3,7 @@ import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import palette from '../../lib/styles/palette';
import AuthForm from '../auth/AuthForm';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
......
import React from 'react';
const LineQR = () => {
return (
<div>
<img
src="https://qr-official.line.me/sid/L/132nudgz.png"
width="130"
/>
</div>
);
};
export default LineQR
......@@ -8,6 +8,7 @@ import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import CircularProgress from '@material-ui/core/CircularProgress';
import styled from 'styled-components';
import LineQR from './LineQR'
const useStyles = makeStyles((theme) => ({
root: {
......@@ -80,6 +81,14 @@ const SettingForm = ({
/>
</Paper>
</Grid>
<Grid container item xs={6}>
<Paper className={classes.paper} elevation={3}>
<h1>QR코드를 찍어주세요</h1>
<LineQR></LineQR>
</Paper>
</Grid>
</Grid>
</div>
);
......
......@@ -75,7 +75,7 @@ const SettingContainer = ({ history }) => {
}
}, [dispatch, user, history]);
useEffect(() => {
if (loading['profile/SYNC_BJID'] == true) {
if (loading['profile/SYNC_BJID']) {
setLoading(true);
} else {
setLoading(false);
......
......@@ -12,15 +12,15 @@
```
npm install
npm update
node index.js
npm start
```
## Example
```
POST http://facerain.dcom.club/profile/getprofile
POST http://www.ossjaksimsamil.ml:3000/profile/getprofile
{
username: 'syw5141',
username : 'redbean096'
}
```
......@@ -40,3 +40,6 @@ POST http://facerain.dcom.club/profile/getprofile
| auth | 로그아웃 | POST | api/auth/logout | [바로가기](/src/api/auth/README.md) | JWT Token |
| auth | 회원가입 | POST | api/auth/register | [바로가기](/src/api/auth/README.md) | None |
| auth | 로그인 확인 | GET | api/auth/check | [바로가기](/src/api/auth/README.md) | None |
| line | 라인 ID 등록 | POST | api/line/linestart | |Line channel access token|
| line | 라인 문제 메시지 발송 | POST | api/line/linestart | |Line channel access token|
......
const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const mongoose = require("mongoose");
const fs = require("fs");
const path = require('path');
const HTTPS = require('https');
const morgan = require("koa-morgan");
const jwtMiddleware = require("./src/lib/jwtMiddleware");
const api = require("./src/api");
const cors = require('@koa/cors'); // for fix CORS Error
require("dotenv").config();
const app = new Koa();
......@@ -15,10 +22,12 @@ const accessLogStream = fs.createWriteStream(__dirname + "/access.log", {
flags: "a",
});
require("dotenv").config();
app.use(cors());
app.use(bodyParser());
app.use(jwtMiddleware);
app.use(morgan("combined", { stream: accessLogStream }));
const { SERVER_PORT, MONGO_URL } = process.env;
const { SERVER_PORT, MONGO_URL, SSLPORT, DOMAIN } = process.env;
router.use("/api", api.routes());
app.use(router.routes()).use(router.allowedMethods());
......@@ -28,6 +37,7 @@ mongoose
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
useCreateIndex : true
})
.then(() => {
console.log("Connected to MongoDB");
......@@ -39,3 +49,19 @@ mongoose
app.listen(SERVER_PORT, () => {
console.log("Server is running on port", process.env.SERVER_PORT);
});
//for Line Channel
try {
const option = {
ca: fs.readFileSync('/etc/letsencrypt/live/' + DOMAIN +'/fullchain.pem'),
key: fs.readFileSync(path.resolve(process.cwd(), '/etc/letsencrypt/live/' + DOMAIN +'/privkey.pem'), 'utf8').toString(),
cert: fs.readFileSync(path.resolve(process.cwd(), '/etc/letsencrypt/live/' + DOMAIN +'/cert.pem'), 'utf8').toString(),
};
HTTPS.createServer(option, app.callback()).listen(SSLPORT, () => {
console.log(`[HTTPS] Server is started on port ${SSLPORT}`);
});
} catch (error) {
console.log('[HTTPS] HTTPS 오류가 발생하였습니다. HTTPS 서버는 실행되지 않습니다.');
console.log(error);
}
......
......@@ -146,6 +146,14 @@
"to-fast-properties": "^2.0.0"
}
},
"@koa/cors": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz",
"integrity": "sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==",
"requires": {
"vary": "^1.1.2"
}
},
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
......@@ -826,6 +834,15 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cron-parser": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.15.0.tgz",
......
......@@ -4,11 +4,13 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@koa/cors": "^3.1.0",
"axios": "^0.19.2",
"bcrypt": "^4.0.1",
"body-parser": "^1.19.0",
"cheerio": "^1.0.0-rc.3",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"eslint-config-prettier": "^6.11.0",
"fs": "^0.0.1-security",
......
......@@ -14,7 +14,7 @@ exports.register = async (ctx) => {
password: Joi.string().required(),
});
const result = Joi.validate(ctx.request.body, schema);
const result = schema.validate(ctx.request.body);
if (result.error) {
ctx.status = 400;
ctx.body = result.error;
......
const Router = require("koa-router");
const auth = new Router();
const authCtrl = require("./auth.ctrl");
auth.post("/login", authCtrl.login);
auth.post("/logout", authCtrl.logout);
auth.post("/register", authCtrl.register);
......
......@@ -6,11 +6,13 @@ const friend = require("./friend");
const notify = require("./notify");
const user = require("./user");
const profile = require("./profile");
const line = require("./line")
api.use("/auth", auth.routes());
api.use("/friend", friend.routes());
api.use("/notify", notify.routes());
api.use("/user", user.routes());
api.use("/profile", profile.routes());
api.use("/line", line.routes());
module.exports = api;
module.exports = api;
\ No newline at end of file
......
const Router = require("koa-router");
const line = new Router();
const lineCtrl = require("./line.ctrl");
line.post("/hook", lineCtrl.linestart);
module.exports = line;
const request = require('request');
const TARGET_URL = 'https://api.line.me/v2/bot/message/reply'
require("dotenv").config();
const TOKEN = process.env.LINE_CHANNEL_KEY;
const Profile = require("../../models/profile");
const problem_set = require("../../data/problem_set");
const compareBJ = require("../../util/compareBJ");
/*
username : default = ""
*/
let username = "";
/*
POST api/line/hook
{
for line reply : recommend and set
}
*/
exports.linestart = async(ctx) => {
var eventObj = ctx.request.body.events[0];
// request log
console.log('======================', new Date() ,'======================');
console.log('[request]', ctx.request.body);
console.log('[request source] ', eventObj.source);
console.log('[request message]', eventObj.message);
console.log('[username]', username);
//first . set User
if(username == "") {
username = eventObj.message.text;
const result = await isExist(username);
if (!result) {
username = "";
request.post(
{
url: TARGET_URL,
headers: {
'Authorization': `Bearer ${TOKEN}`
},
json: {
"replyToken":eventObj.replyToken,
"messages":[
{
"type":"text",
"text": "작심삼일 사이트의 ID값이 올바르지 않습니다. 다시 입력해주세요."
}
]
}
},(body) => {
console.log(body)
});
} else {
request.post({
url: TARGET_URL,
headers: {
'Authorization': `Bearer ${TOKEN}`
},
json: {
"replyToken":eventObj.replyToken,
"messages":[
{
"type":"text",
"text": eventObj.message.text + " : 정상 등록 되었습니다."
}
]
}
},(body) => {
console.log(body)
});
}
};
//second. can reset username
if(eventObj.message.text == "reset ID") {
username = "";
request.post(
{
url: TARGET_URL,
headers: {
'Authorization': `Bearer ${TOKEN}`
},
json: {
"replyToken": eventObj.replyToken,
"messages":[
{
"type":"text",
"text": "작심삼일 사이트의 ID가 초기화 되었습니다."
}
]
}
},(body) => {
console.log(body)
});
}
//main : recommend Problem
if(eventObj.message.text == "문제") {
console.log("문제를 추천합니다.");
let recommendData = await lineRecommend(username);
recommendBJ(eventObj.replyToken, recommendData);
console.log(recommendData);
}
}
//for recommend message
function recommendBJ(replyToken, recommendData) {
var recommendProblem = "오늘의 추천문제는 " + recommendData.problem_title + "입니다.";
var problemURL = "https://www.boj.kr/" + recommendData.problem_number;
request.post(
{
url: TARGET_URL,
headers: {
'Authorization': `Bearer ${TOKEN}`
},
json: {
"replyToken":replyToken,
"messages":[
{
"type":"text",
"text": recommendProblem
},
{
"type":"text",
"text": problemURL
}
]
}
},(body) => {
console.log(body)
});
}
async function lineRecommend (username) {
const profile = await Profile.findByUsername(username);
if (!profile) {
console.log("401");
return;
}
const unsolved_data = await compareBJ.compareBJ(
profile.getBJdata(),
problem_set.problem_set
);
const recommendData = await compareBJ.randomItem(unsolved_data);
if (!recommendData) {
console.log("402");
return;
}
return recommendData;
};
async function isExist(username) {
const profile = await Profile.findByUsername(username);
if (!profile) {
return false;
}
return true;
}
const Router = require("koa-router");
const profile = new Router();
const profileCtrl = require("./profile.ctrl");
profile.post("/solved:id");
profile.get("/solvednum:id");
profile.post("/recommend", profileCtrl.recommend);
profile.patch("/syncBJ", profileCtrl.syncBJ);
profile.post("/setprofile", profileCtrl.setProfile);
profile.post("/getprofile", profileCtrl.getProfile);
module.exports = profile;
......
......@@ -51,7 +51,7 @@ exports.setProfile = async (ctx) => {
})
.unknown();
console.log(ctx.request.body);
const result = Joi.validate(ctx.request.body, schema);
const result = schema.validate(ctx.request.body);
if (result.error) {
ctx.status = 400;
ctx.body = result.error;
......@@ -118,14 +118,16 @@ POST /api/proflie/recommend
*/
exports.recommend = async (ctx) => {
const { username } = ctx.request.body;
if (!username) {
console.log('no given username');
ctx.status = 401;
return;
}
try {
console.log('finding username:', username);
const profile = await Profile.findByUsername(username);
if (!profile) {
console.log('no profile');
ctx.status = 401;
return;
}
......@@ -137,5 +139,7 @@ exports.recommend = async (ctx) => {
//데이터가 비었을 떄 예외처리 필요
} catch (e) {
ctx.throw(500, e);
} finally {
console.log('successfully sent user recommendation')
}
};
......