송용우

Merge commit '38227cf5' into feature/frontend_page

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
},
}));
const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => {
const classes = useStyles();
return (
<div>
<form onSubmit={onGoalNumSubmit}>
<TextField
name="goalNum"
type="number"
onChange={onChange}
value={profile.goalNum}
placeholder="일일 목표"
label="일일 목표"
InputLabelProps={{
shrink: true,
}}
/>
<Button variant="outlined" type="submit">
등록
</Button>
</form>
</div>
);
};
export default GoalNumForm;
......@@ -2,9 +2,12 @@ import React from 'react';
import palette from '../../lib/styles/palette';
import BJIDForm from './BJIDForm';
import SlackForm from './SlackForm';
import GoalNumForm from './GoalNumForm';
import { makeStyles } from '@material-ui/core/styles';
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';
const useStyles = makeStyles((theme) => ({
root: {
......@@ -18,15 +21,29 @@ const useStyles = makeStyles((theme) => ({
},
}));
const LoadingParentStyle = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 20px;
`;
const SettingForm = ({
onChange,
onBJIDSubmit,
onSlackURLSubmit,
profile,
onSyncBJIDSubmit,
onGoalNumSubmit,
isLoading,
}) => {
const classes = useStyles();
return (
return isLoading ? (
<LoadingParentStyle>
<CircularProgress className={classes.loading} />
</LoadingParentStyle>
) : (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
......@@ -54,6 +71,16 @@ const SettingForm = ({
/>
</Paper>
</Grid>
<Grid container item xs={12}>
<Paper className={classes.paper} elevation={3}>
<GoalNumForm
profile={profile}
onChange={onChange}
onGoalNumSubmit={onGoalNumSubmit}
/>
</Paper>
</Grid>
</Grid>
</div>
);
......
......@@ -3,14 +3,15 @@ import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import HomeForm from '../../components/home/HomeForm';
import { getPROFILE } from '../../modules/profile';
import { analyzeBJ } from '../../lib/util/analyzeBJ';
const HomeContainer = ({ history }) => {
const dispatch = useDispatch();
const { user, profile } = useSelector(({ user, profile }) => ({
user: user.user,
profile: profile,
}));
useEffect(() => {}, [profile.solvedBJ]);
useEffect(() => {
console.log(profile);
}, [profile.solvedBJ]);
useEffect(() => {
if (user) {
let username = user.username;
......
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import {
......@@ -8,15 +8,20 @@ import {
syncBJID,
initializeProfile,
setSLACK,
setGOALNUM,
} from '../../modules/profile';
import SettingForm from '../../components/setting/SettingForm';
const SettingContainer = ({ history }) => {
const [isLoading, setLoading] = useState(false);
const dispatch = useDispatch();
const { user, profile } = useSelector(({ user, profile }) => ({
user: user.user,
profile: profile,
}));
const { user, profile, loading } = useSelector(
({ user, profile, loading }) => ({
user: user.user,
profile: profile,
loading: loading,
}),
);
const onChange = (e) => {
const { value, name } = e.target;
......@@ -33,6 +38,13 @@ const SettingContainer = ({ history }) => {
let username = profile.username;
dispatch(syncBJID({ username }));
};
const onGoalNumSubmit = (e) => {
e.preventDefault();
let username = profile.username;
let goalNum = profile.goalNum;
dispatch(setGOALNUM({ username, goalNum }));
};
const onSlackURLSubmit = (e) => {
e.preventDefault();
let username = profile.username;
......@@ -60,6 +72,13 @@ const SettingContainer = ({ history }) => {
};
}
}, [dispatch, user, history]);
useEffect(() => {
if (loading['profile/SYNC_BJID'] == true) {
setLoading(true);
} else {
setLoading(false);
}
}, [dispatch, loading]);
return (
<SettingForm
......@@ -68,7 +87,9 @@ const SettingContainer = ({ history }) => {
onBJIDSubmit={onBJIDSubmit}
onSyncBJIDSubmit={onSyncBJIDSubmit}
onSlackURLSubmit={onSlackURLSubmit}
onGoalNumSubmit={onGoalNumSubmit}
profile={profile}
isLoading={isLoading}
></SettingForm>
);
};
......
......@@ -17,6 +17,11 @@ const [
SET_SLACK_FAILURE,
] = createRequestActionTypes('/profile/SET_SLACK');
const [
SET_GOALNUM,
SET_GOALNUM_SUCCESS,
SET_GOALNUM_FAILURE,
] = createRequestActionTypes('/profile/SET_GOALNUM');
const [
GET_PROFILE,
GET_PROFILE_SUCCESS,
GET_PROFILE_FAILURE,
......@@ -31,6 +36,7 @@ export const initializeProfile = createAction(INITIALIZE);
export const syncBJID = createAction(SYNC_BJID, ({ username }) => ({
username,
}));
export const setSLACK = createAction(
SET_SLACK,
({ username, slackWebHookURL }) => ({
......@@ -38,6 +44,14 @@ export const setSLACK = createAction(
slackWebHookURL,
}),
);
export const setGOALNUM = createAction(
SET_GOALNUM,
({ username, goalNum }) => ({
username,
goalNum,
}),
);
export const setBJID = createAction(SET_BJID, ({ username, userBJID }) => ({
username,
userBJID,
......@@ -58,16 +72,21 @@ const initialState = {
friendList: [],
profileError: '',
slackWebHookURL: '',
solvedBJ_date: '',
goalNum: '',
};
const getPROFILESaga = createRequestSaga(GET_PROFILE, profileAPI.getPROFILE);
const setBJIDSaga = createRequestSaga(SET_BJID, profileAPI.setBJID);
const setSLACKSaga = createRequestSaga(SET_SLACK, profileAPI.setPROFILE);
const setGOALNUMSaga = createRequestSaga(SET_GOALNUM, profileAPI.setPROFILE);
const syncBJIDSaga = createRequestSaga(SYNC_BJID, profileAPI.syncBJ);
export function* profileSaga() {
yield takeLatest(SET_BJID, setBJIDSaga);
yield takeLatest(GET_PROFILE, getPROFILESaga);
yield takeLatest(SYNC_BJID, syncBJIDSaga);
yield takeLatest(SET_SLACK, setSLACKSaga);
yield takeLatest(SET_GOALNUM, setGOALNUMSaga);
}
export default handleActions(
......@@ -80,7 +99,15 @@ export default handleActions(
[GET_PROFILE_SUCCESS]: (
state,
{
payload: { username, userBJID, solvedBJ, friendList, slackWebHookURL },
payload: {
username,
userBJID,
solvedBJ,
friendList,
slackWebHookURL,
solvedBJ_date,
goalNum,
},
},
) => ({
...state,
......@@ -90,6 +117,8 @@ export default handleActions(
friendList: friendList,
profileError: null,
slackWebHookURL: slackWebHookURL,
solvedBJ_date: solvedBJ_date,
goalNum: goalNum,
}),
[GET_PROFILE_FAILURE]: (state, { payload: error }) => ({
...state,
......@@ -114,6 +143,14 @@ export default handleActions(
...state,
profileError: error,
}),
[SET_GOALNUM_SUCCESS]: (state, { payload: { goalNum } }) => ({
...state,
goalNum: goalNum,
}),
[SET_GOALNUM_FAILURE]: (state, { payload: error }) => ({
...state,
profileError: error,
}),
[SYNC_BJID_SUCCESS]: (state, { payload: { solvedBJ } }) => ({
...state,
solvedBJ,
......
......@@ -18,22 +18,22 @@
## API Table
| group | description | method | URL | Detail | Auth |
| ------- | --------------------------- | --------- | ------------------------ | -------- | --------- |
| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | 바로가기 | None |
| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | 바로가기 | None |
| profile | 유저 정보 수정 | POST | api/profile/setprofile | 바로가기 | JWT TOKEN |
| profile | 유저 정보 받아오기 | POST | api/profile/getprofile | 바로가기 | JWT |
| profile | 추천 문제 조회 | GET | api/profile/recommend:id | 바로가기 | None |
| notify | 슬랙 메시지 전송 요청 | POST | api/notify/ |
| slack | 바로가기 | Jwt Token |
| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
| auth | 로그아웃 | POST | api/auth/logout | 바로가기 | JWT Token |
| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
| group | description | method | URL | Detail | Auth |
| ------- | --------------------------------- | ------ | ----------------------- | -------- | --------- |
| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | 바로가기 | None |
| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | 바로가기 | None |
| profile | 유저 정보 수정 | POST | api/profile/setprofile | 바로가기 | JWT TOKEN |
| profile | 유저 정보 받아오기 | POST | api/profile/getprofile | 바로가기 | JWT |
| profile | 추천 문제 조회 | POST | api/profile/recommend | 바로가기 | None |
| notify | 슬랙 메시지 전송 요청 (성취여부) | POST | api/notify/goal | 바로가기 | Jwt Token |
| notify | 슬랙 메시지 전송 요청 (문제 추천) | POST | api/notify/recommend | 바로가기 | None |
| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
| auth | 로그아웃 | POST | api/auth/logout | 바로가기 | JWT Token |
| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
......
This diff is collapsed. Click to expand it.
......@@ -3,7 +3,7 @@ const api = new Router();
const auth = require("./auth");
const friend = require("./friend");
const notify = require("./profile");
const notify = require("./notify");
const user = require("./user");
const profile = require("./profile");
......
const Router = require("koa-router");
const notify = new Router();
notify.post("/slack");
const slackCtrl = require("./slack.ctrl");
notify.post("/slack/goal", slackCtrl.slackGoal);
notify.post("/slack/recommend", slackCtrl.slackRecommend);
module.exports = notify;
......
const Profile = require("../../models/profile");
const sendSlack = require("../../util/sendSlack");
const problem_set = require("../../data/problem_set");
const compareBJ = require("../../util/compareBJ");
/*
POST api/notify/slack/goal
{
username: "username"
}
*/
exports.slackGoal = async (ctx) => {
try {
const { username } = ctx.request.body;
const profile = await Profile.findByUsername(username);
if (!profile) {
ctx.status = 401;
return;
}
let slackURL = profile.getslackURL();
if (!slackURL) {
ctx.status = 401;
return;
}
let goalNum = profile.getgoalNum();
let todayNum = profile.getTodaySovled();
let message = "";
if (goalNum < todayNum) {
message =
"오늘의 목표 " +
goalNum +
"문제 중 " +
todayNum +
"문제를 풀었습니다." +
"\n" +
"잘하셨습니다!";
} else {
message =
"오늘의 목표 " +
goalNum +
"문제 중 " +
todayNum +
"문제를 풀었습니다." +
"\n" +
"분발하세요!";
}
sendSlack.send(message, slackURL);
} catch (e) {
ctx.throw(500, e);
}
};
/*
POST api/notify/slack/recommend
{
username: "username"
}
*/
exports.slackRecommend = async (ctx) => {
try {
console.log("1");
const { username } = ctx.request.body;
const profile = await Profile.findByUsername(username);
if (!profile) {
ctx.status = 401;
return;
}
let slackURL = profile.getslackURL();
if (!slackURL) {
ctx.status = 401;
return;
}
let unsolved_data = compareBJ.compareBJ(
profile.getBJdata(),
problem_set.problem_set
);
let recommendData = compareBJ.randomItem(unsolved_data);
if (!recommendData) {
ctx.status = 401;
return;
}
let message =
"오늘의 추천 문제는 " +
recommendData.problem_number +
"번 " +
" <https://www.boj.kr/" +
recommendData.problem_number +
"|" +
recommendData.problem_title +
">" +
" 입니다.";
sendSlack.send(message, slackURL);
} catch (e) {
ctx.throw(500, e);
}
};
const Router = require("koa-router");
const profile = new Router();
const profileCtrl = require("./profile.ctrl");
profile.post("/solved:id");
profile.get("/solvednum:id");
profile.get("/recommendps:id");
profile.post("/recommend", profileCtrl.recommend);
profile.patch("/syncBJ", profileCtrl.syncBJ);
profile.post("/setprofile", profileCtrl.setProfile);
profile.post("/getprofile", profileCtrl.getProfile);
......
......@@ -2,6 +2,9 @@ const Profile = require("../../models/profile");
const mongoose = require("mongoose");
const getBJ = require("../../util/getBJ");
const Joi = require("joi");
const analyzeBJ = require("../../util/analyzeBJ");
const compareBJ = require("../../util/compareBJ");
const problem_set = require("../../data/problem_set");
const { ObjectId } = mongoose.Types;
......@@ -47,7 +50,7 @@ exports.setProfile = async (ctx) => {
//freindList: Joi.array().items(Joi.string()),
})
.unknown();
console.log(ctx.request.body);
const result = Joi.validate(ctx.request.body, schema);
if (result.error) {
ctx.status = 400;
......@@ -95,9 +98,10 @@ exports.syncBJ = async function (ctx) {
}
const BJID = await profile.getBJID();
let BJdata = await getBJ.getBJ(BJID);
let BJdata_date = await analyzeBJ.analyzeBJ(BJdata);
const updateprofile = await Profile.findOneAndUpdate(
{ username: username },
{ solvedBJ: BJdata },
{ solvedBJ: BJdata, solvedBJ_date: BJdata_date },
{ new: true }
).exec();
ctx.body = updateprofile;
......@@ -105,3 +109,33 @@ exports.syncBJ = async function (ctx) {
ctx.throw(500, e);
}
};
/*
POST /api/proflie/recommend
{
username: 'userid'
}
*/
exports.recommend = async (ctx) => {
const { username } = ctx.request.body;
if (!username) {
ctx.status = 401;
return;
}
try {
const profile = await Profile.findByUsername(username);
if (!profile) {
ctx.status = 401;
return;
}
let unsolved_data = compareBJ.compareBJ(
profile.getBJdata(),
problem_set.problem_set
);
ctx.body = compareBJ.randomItem(unsolved_data);
//데이터가 비었을 떄 예외처리 필요
} catch (e) {
ctx.throw(500, e);
}
};
......
This diff is collapsed. Click to expand it.
......@@ -6,8 +6,10 @@ const ProfileSchema = new Schema({
username: { type: String, required: true, unique: true },
userBJID: String,
solvedBJ: Object,
solvedBJ_date: Object,
friendList: [String],
slackWebHookURL: String,
goalNum: Number,
});
ProfileSchema.statics.findByUsername = function (username) {
return this.findOne({ username });
......@@ -15,6 +17,20 @@ ProfileSchema.statics.findByUsername = function (username) {
ProfileSchema.methods.getBJID = function () {
return this.userBJID;
};
ProfileSchema.methods.getBJdata = function () {
return this.solvedBJ;
};
ProfileSchema.methods.getslackURL = function () {
return this.slackWebHookURL;
};
ProfileSchema.methods.getgoalNum = function () {
return this.goalNum;
};
ProfileSchema.methods.getTodaySovled = function () {
if (this.solvedBJ_date) {
return this.solvedBJ_date.presentNum;
}
};
ProfileSchema.methods.serialize = function () {
const data = this.toJSON();
......
/*
2. 현재 날짜와의 차이 =>
3. 오늘 푼 문제 => 앞에서부터 순회하면서 데이트 같은거 찾기
3. 최근 일주일간 푼 문제 수 => 앞에서부터 순회하면서 - 값이
4. 추천 문제 => 정규 셋에서 없는거 찾기
5. 날짜별로 묶기.
데이터베이스에서 처리하자
*/
let moment = require('moment');
let moment = require("moment");
exports.analyzeBJ = function (solvedBJ) {
try {
if (solvedBJ) {
console.log(solvedBJ[0]);
let presentDate = moment();
let presentDate_str = presentDate.format('YYYYMMDD');
let latestDate = moment(solvedBJ[0].solved_date, 'YYYYMMDD');
let difflatest = presentDate.diff(latestDate, 'days');
let presentDate_str = presentDate.format("YYYYMMDD");
let latestDate = moment(solvedBJ[0].solved_date, "YYYYMMDD");
let difflatest = presentDate.diff(latestDate, "days");
let solvedBJbyDATE = {};
for (let i = 0; i < solvedBJ.length; i++) {
......@@ -34,13 +24,13 @@ exports.analyzeBJ = function (solvedBJ) {
? solvedBJbyDATE[presentDate_str].length
: 0;
let returnOBJ = {
latestDate: latestDate.format('YYYYMMDD'),
latestDate: latestDate.format("YYYYMMDD"),
difflatest: difflatest,
latestNum: latestNum,
presentNum: presentNum,
solvedBJbyDATE: solvedBJbyDATE,
};
console.log(returnOBJ);
return returnOBJ;
}
} catch (e) {
......
/*
집중을 해보자.
새거와 데이터가 있다.
데이터 기준으로 새거에 자료가 있으면 넘어가기
없으면 새 배열에 추가
키만 모아둔 리스트를 만들자.
그렇게 해서
반복은 새거 길이만큼
데이터에 있으면 추가 X
없으면 추가
그렇게 반환
*/
exports.compareBJ = function (solvedBJ_new, problem_set) {
try {
let new_obj = [];
for (let i = 0; i < solvedBJ.length; i++) {
if (solvedBJ_new[i].problem_number in problem_set) {
new_obj.push(solvedBJ_new[i]);
for (let i = 0; i < problem_set.length; i++) {
let found = false;
for (let j = 0; j < solvedBJ_new.length; j++) {
if (solvedBJ_new[j].problem_number == problem_set[i].problem_number) {
found = true;
break;
}
}
if (!found) {
new_obj.push(problem_set[i]);
}
}
console.log(new_obj);
return new_obj;
} catch (e) {
console.log(e);
}
};
exports.randomItem = function (a) {
return a[Math.floor(Math.random() * a.length)];
};
......
const Slack = require("slack-node"); // 슬랙 모듈 사용
/*
const webhookUri =
"https://hooks.slack.com/services/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"; // Webhook URL
"https://hooks.slack.com/services/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"; // Webhook URL
*/
const slack = new Slack();
slack.setWebhook(webhookUri);
const send = async (message) => {
exports.send = async (message, webhookUri) => {
const slack = new Slack();
slack.setWebhook(webhookUri);
slack.webhook(
{
text: message,
......@@ -16,5 +17,3 @@ const send = async (message) => {
}
);
};
send("hello");
......
var getBJ = require("./getBJ");
var fs = require("fs");
let dataset = [
"1517",
"2448",
"1891",
"1074",
"2263",
"1780",
"11728",
"10816",
"10815",
"2109",
"1202",
"1285",
"2138",
"1080",
"11399",
"1931",
"11047",
"15666",
"15665",
"15664",
"15663",
"15657",
"15656",
"15655",
"15654",
"15652",
"15651",
"15650",
"15649",
"6603",
"10971",
"10819",
"10973",
"10974",
"10972",
"7576",
"1248",
"2529",
"15661",
"14501",
"1759",
"14391",
"14889",
"1182",
"11723",
"1748",
"6064",
"1107",
"3085",
"2309",
"1748",
"14500",
"1107",
"1476",
"3085",
"2309",
"1261",
"13549",
"14226",
"13913",
"1697",
"1967",
"1167",
"11725",
"2250",
"1991",
"7562",
"2178",
"4963",
"2667",
"1707",
"11724",
"1260",
"13023",
"11652",
"1377",
"11004",
"10825",
"2751",
"9461",
"1699",
"9095",
"2225",
"2133",
"11727",
"11726",
"1463",
"2748",
"2747",
"11656",
"10824",
"2743",
"10820",
"10808",
"11655",
"11720",
"1008",
"10951",
"2557",
"1021",
"1966",
"2164",
"10799",
"17413",
"10866",
"1158",
"10845",
"1406",
"1874",
"9012",
"9093",
"10828",
"11721",
"11719",
"11718",
"10953",
"2558",
"10814",
"1181",
"11651",
"11650",
"1427",
"2108",
"10989",
"2751",
"2750",
"1436",
"1018",
"7568",
"2231",
"2798",
"1002",
"3053",
"4153",
"3009",
"1085",
"9020",
"4948",
"1929",
"2581",
"1978",
"2292",
"6064",
"2775",
"10250",
"2869",
"1011",
"1193",
"2839",
"1712",
"1316",
"2941",
"5622",
"2908",
"1152",
"1157",
"2675",
"10809",
"11720",
"11654",
"11729",
"2447",
"3052",
"10818",
"10872",
"10870",
"1065",
"4673",
"15596",
"4344",
"2920",
"8958",
"1546",
"2577",
"2562",
"1110",
"10951",
"10952",
"10871",
"2439",
"2438",
"11022",
"11021",
"2742",
"2741",
"15552",
"8393",
"10950",
"2739",
"10817",
"2884",
"2753",
"9498",
"1330",
"2588",
"10430",
"10869",
"1008",
"10998",
"7287",
"10172",
"10171",
"10718",
"1001",
"1000",
"2557",
];
const test = async (userid) => {
let lst = await getBJ.getBJ(userid);
let return_lst = [];
......@@ -217,7 +8,7 @@ const test = async (userid) => {
return_lst.push(lst[i].problem_number);
}
var stringJson = JSON.stringify(return_lst) + "\n";
var stringJson = JSON.stringify(lst) + "\n";
fs.open("test.json", "a", "666", function (err, id) {
if (err) {
console.log("file open err!!");
......
This diff is collapsed. Click to expand it.