송용우

Merge commit 'ecda5049' into feature/frontend_page

Showing 41 changed files with 823 additions and 345 deletions
1 +
2 +MIT License
3 +
4 +Copyright (c) 2020 Yong-Woo Song
5 +
6 +Permission is hereby granted, free of charge, to any person obtaining a copy
7 +of this software and associated documentation files (the "Software"), to deal
8 +in the Software without restriction, including without limitation the rights
9 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +copies of the Software, and to permit persons to whom the Software is
11 +furnished to do so, subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice shall be included in all
14 +copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 +SOFTWARE.
...\ No newline at end of file ...\ No newline at end of file
1 +# Jaksimsamil
2 +
3 +![issue badge](https://img.shields.io/github/issues/FacerAin/OSS-Jaksimsamil)
4 +![fork badge](https://img.shields.io/github/forks/FacerAin/OSS-Jaksimsamil)
5 +![star badge](https://img.shields.io/github/stars/FacerAin/OSS-Jaksimsamil)
6 +![license badge](https://img.shields.io/github/license/FacerAin/OSS-Jaksimsamil)
7 +
8 +## Project Overview
9 +
10 +> **Jaksaimsamil Algorithm Study Helper Service**
11 +>
12 +> 작심삼일 알고리즘 문제풀이 도우미 서비스<br/>
13 +>
14 +> > 알고리즘 문제 풀이 스터디를 꾸준히 할 수 있게 돕는 웹 서비스입니다.
15 +> > <br> [링크](http://facerain.dcom.club)에서 직접 사용해 보세요!
16 +
17 +![그림1](https://user-images.githubusercontent.com/16442978/85690047-236d1d00-b70e-11ea-8d2b-480593c0daf3.png)
18 +
19 +![그림2](https://user-images.githubusercontent.com/16442978/85690058-2536e080-b70e-11ea-98cd-45fdf04084ce.png)
20 +
21 +## Features (ver.1.0.0)
22 +
23 +- 회원가입/로그인 제공
24 +- Online Judge 연동 가능 (Baekjoon)
25 +- 나의 학습 현황 한눈에 보기
26 +- 추천 문제 제공
27 +- Slack 알리미
28 +
29 +## Upcoming Features
30 +
31 +- 친구 추가
32 +- 친구와의 경쟁
33 +- 그룹 추가
34 +- 그룹 추천
35 +- 개선된 문제 추천 (사용자 실력 맞춤형)
36 +
37 +## Usages
38 +
39 +#### 회원
40 +
41 +1. 로그인하여 서비스에 접속 할 수 있습니다.
42 +2. 서비스가 처음이라면, 회원가입을 하세요.
43 + <br>
44 +
45 +#### 설정
46 +
47 +1. 백준 아이디를 등록하고 동기화하세요. [상세]()
48 +2. 슬랙 HOOK URL을 등록하세요. [상세]()
49 +3. 일일 목표량을 등록하세요.
50 +
51 +## Getting Started
52 +
53 +1. Clone
54 +
55 +```
56 +git clone https://github.com/FacerAin/OSS-Jaksimsamil.git
57 +```
58 +
59 +2. Install MongoDB(Ubuntu)
60 +
61 +```
62 +sudo apt-get update
63 +sudo apt-get install -y mongodb-org
64 +sudo service mongod start
65 +```
66 +
67 +3. Set Serverfile
68 +
69 +```
70 +cd Jaksimsamil-server
71 +touch .env
72 +---TYPE THIS IN FILE----
73 +SERVER_PORT= ###
74 +MONGO_URL= ###
75 +JWT_SECRET= ###
76 +```
77 +
78 +4. Start Node Server
79 +
80 +```
81 +cd Jaksimsamil-server
82 +sudo npm install
83 +npm start
84 +```
85 +
86 +[링크](/jaksimsamil-server/README.md)에서 API 제공 목록을 볼 수 있습니다.
87 +<br>
88 +
89 +5. Set Front-end page
90 +
91 +```
92 +cd Jaksimsamil-server
93 +sudo npm install
94 +npm start #Start React
95 +```
96 +
97 +## Contributing
98 +
99 +컨트리뷰션은 언제나 환영입니다. 다음 절차를 지켜주세요!
100 +
101 +1. Fork the Project
102 +2. Create your Feature Branch
103 +3. Commit our changes
104 +4. Push to Branch
105 +5. Open a Pull Request
106 +
107 +## License
108 +
109 +- MIT LICENCE
......
1 +# Security Policy
2 +
3 +## Supported Versions
4 +
5 +Use this section to tell people about which versions of your project are
6 +currently being supported with security updates.
7 +
8 +| Version | Supported |
9 +| ------- | ------------------ |
10 +| 5.1.x | :white_check_mark: |
11 +| 5.0.x | :x: |
12 +| 4.0.x | :white_check_mark: |
13 +| < 4.0 | :x: |
14 +
15 +## Reporting a Vulnerability
16 +
17 +Use this section to tell people how to report a vulnerability.
18 +
19 +Tell them where to go, how often they can expect to get an update on a
20 +reported vulnerability, what to expect if the vulnerability is accepted or
21 +declined, etc.
This diff could not be displayed because it is too large.
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
16 "react-dom": "^16.13.1", 16 "react-dom": "^16.13.1",
17 "react-redux": "^7.2.0", 17 "react-redux": "^7.2.0",
18 "react-router-dom": "^5.2.0", 18 "react-router-dom": "^5.2.0",
19 - "react-scripts": "3.4.1", 19 + "react-scripts": "^3.4.3",
20 "redux": "^4.0.5", 20 "redux": "^4.0.5",
21 "redux-actions": "^2.6.5", 21 "redux-actions": "^2.6.5",
22 "redux-devtools-extension": "^2.13.8", 22 "redux-devtools-extension": "^2.13.8",
......
...@@ -3,42 +3,111 @@ import { makeStyles } from '@material-ui/core/styles'; ...@@ -3,42 +3,111 @@ import { makeStyles } from '@material-ui/core/styles';
3 import Paper from '@material-ui/core/Paper'; 3 import Paper from '@material-ui/core/Paper';
4 import Grid from '@material-ui/core/Grid'; 4 import Grid from '@material-ui/core/Grid';
5 import palette from '../../lib/styles/palette'; 5 import palette from '../../lib/styles/palette';
6 +import AuthForm from '../auth/AuthForm';
6 const useStyles = makeStyles((theme) => ({ 7 const useStyles = makeStyles((theme) => ({
7 root: { 8 root: {
8 flexGrow: 1, 9 flexGrow: 1,
9 background: palette.gray[2], 10 background: palette.gray[2],
11 + padding: theme.spacing(8),
10 }, 12 },
11 paper: { 13 paper: {
12 - padding: theme.spacing(2), 14 + padding: theme.spacing(8),
15 + margin: 'auto',
13 textAlign: 'center', 16 textAlign: 'center',
14 color: theme.palette.text.secondary, 17 color: theme.palette.text.secondary,
15 }, 18 },
16 })); 19 }));
17 -const HomeForm = () => { 20 +const HomeForm = ({ PSdata, goalNum }) => {
18 const classes = useStyles(); 21 const classes = useStyles();
19 - return ( 22 + return PSdata ? (
20 <div className={classes.root}> 23 <div className={classes.root}>
21 - <Grid container spacing={3}> 24 + <Grid container spacing={5}>
22 <Grid item xs={12}> 25 <Grid item xs={12}>
23 - <Paper className={classes.paper}>xs=12</Paper> 26 + <Paper className={classes.paper}>
27 + <h1>{PSdata.recommend_data.problem_number}</h1>
28 + <h1>{PSdata.recommend_data.problem_title}</h1>
29 + <a
30 + href={'http://www.boj.kr/' + PSdata.recommend_data.problem_number}
31 + >
32 + 바로가기
33 + </a>
34 +
35 + <h3>오늘의 추천 문제</h3>
36 + </Paper>
37 + </Grid>
38 + <Grid item xs={6}>
39 + <Paper className={classes.paper}>
40 + <h1>{PSdata.presentNum + '/' + goalNum}</h1>
41 + <h3>오늘 문제</h3>
42 + </Paper>
24 </Grid> 43 </Grid>
25 <Grid item xs={6}> 44 <Grid item xs={6}>
26 - <Paper className={classes.paper}>xs=6</Paper> 45 + <Paper className={classes.paper}>
46 + <h1>{PSdata.latestSolve.problem_number}</h1>
47 + <h1>{PSdata.latestSolve.problem_title}</h1>
48 + <h3>마지막으로 문제</h3>
49 + </Paper>
50 + </Grid>
51 +
52 + <Grid item xs={4}>
53 + <Paper className={classes.paper}>
54 + <h1>{PSdata.weekNum}</h1>
55 + <h3>7</h3>
56 + </Paper>
57 + </Grid>
58 + <Grid item xs={4}>
59 + <Paper className={classes.paper}>
60 + <h1>{PSdata.monthNum}</h1>
61 + <h3>30</h3>
62 + </Paper>
63 + </Grid>
64 + <Grid item xs={4}>
65 + <Paper className={classes.paper}>
66 + <h1>{PSdata.totalNum}</h1>
67 + <h3>전체</h3>
68 + </Paper>
69 + </Grid>
70 + </Grid>
71 + </div>
72 + ) : (
73 + <div className={classes.root}>
74 + <Grid container spacing={5}>
75 + <Grid item xs={12}>
76 + <Paper className={classes.paper}>
77 + <h1></h1>
78 + <h3>오늘의 추천 문제</h3>
79 + </Paper>
27 </Grid> 80 </Grid>
28 <Grid item xs={6}> 81 <Grid item xs={6}>
29 - <Paper className={classes.paper}>xs=6</Paper> 82 + <Paper className={classes.paper}>
83 + <h1></h1>
84 + <h3>오늘</h3>
85 + </Paper>
30 </Grid> 86 </Grid>
31 - <Grid item xs={3}> 87 + <Grid item xs={6}>
32 - <Paper className={classes.paper}>xs=3</Paper> 88 + <Paper className={classes.paper}>
89 + <h1></h1>
90 + <h3>마지막 </h3>
91 + </Paper>
33 </Grid> 92 </Grid>
34 - <Grid item xs={3}> 93 +
35 - <Paper className={classes.paper}>xs=3</Paper> 94 + <Grid item xs={4}>
95 + <Paper className={classes.paper}>
96 + <h1></h1>
97 + <h3>7</h3>
98 + </Paper>
36 </Grid> 99 </Grid>
37 - <Grid item xs={3}> 100 + <Grid item xs={4}>
38 - <Paper className={classes.paper}>xs=3</Paper> 101 + <Paper className={classes.paper}>
102 + <h1></h1>
103 + <h3>30</h3>
104 + </Paper>
39 </Grid> 105 </Grid>
40 - <Grid item xs={3}> 106 + <Grid item xs={4}>
41 - <Paper className={classes.paper}>xs=3</Paper> 107 + <Paper className={classes.paper}>
108 + <h1></h1>
109 + <h3>전체</h3>
110 + </Paper>
42 </Grid> 111 </Grid>
43 </Grid> 112 </Grid>
44 </div> 113 </div>
......
...@@ -9,13 +9,16 @@ const useStyles = makeStyles((theme) => ({ ...@@ -9,13 +9,16 @@ const useStyles = makeStyles((theme) => ({
9 margin: theme.spacing(1), 9 margin: theme.spacing(1),
10 }, 10 },
11 }, 11 },
12 + button: {
13 + margin: theme.spacing(1),
14 + },
12 })); 15 }));
13 16
14 const BJIDForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => { 17 const BJIDForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => {
15 const classes = useStyles(); 18 const classes = useStyles();
16 return ( 19 return (
17 <div> 20 <div>
18 - <form onSubmit={onBJIDSubmit}> 21 + <form>
19 <TextField 22 <TextField
20 name="userBJID" 23 name="userBJID"
21 onChange={onChange} 24 onChange={onChange}
...@@ -23,11 +26,21 @@ const BJIDForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => { ...@@ -23,11 +26,21 @@ const BJIDForm = ({ onChange, onBJIDSubmit, profile, onSyncBJIDSubmit }) => {
23 placeholder="백준 아이디" 26 placeholder="백준 아이디"
24 label="백준 아이디" 27 label="백준 아이디"
25 /> 28 />
26 - <Button variant="outlined" type="submit"> 29 + </form>
30 + <Button
31 + className={classes.button}
32 + variant="outlined"
33 + onClick={onBJIDSubmit}
34 + color="primary"
35 + >
27 등록 36 등록
28 </Button> 37 </Button>
29 - </form> 38 + <Button
30 - <Button variant="outlined" onClick={onSyncBJIDSubmit}> 39 + className={classes.button}
40 + variant="outlined"
41 + onClick={onSyncBJIDSubmit}
42 + color="secondary"
43 + >
31 동기화 44 동기화
32 </Button> 45 </Button>
33 </div> 46 </div>
......
1 +import React from 'react';
2 +import { makeStyles } from '@material-ui/core/styles';
3 +
4 +import Button from '@material-ui/core/Button';
5 +import TextField from '@material-ui/core/TextField';
6 +
7 +const useStyles = makeStyles((theme) => ({
8 + root: {
9 + '& > *': {
10 + margin: theme.spacing(1),
11 + },
12 + },
13 + button: {
14 + margin: theme.spacing(1),
15 + },
16 +}));
17 +
18 +const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => {
19 + const classes = useStyles();
20 + return (
21 + <div>
22 + <form>
23 + <TextField
24 + name="goalNum"
25 + type="number"
26 + onChange={onChange}
27 + value={profile.goalNum}
28 + placeholder="일일 목표"
29 + label="일일 목표"
30 + InputLabelProps={{
31 + shrink: true,
32 + }}
33 + />
34 + </form>
35 + <Button
36 + className={classes.button}
37 + onClick={onGoalNumSubmit}
38 + color="primary"
39 + variant="outlined"
40 + >
41 + 등록
42 + </Button>
43 + </div>
44 + );
45 +};
46 +
47 +export default GoalNumForm;
...@@ -2,40 +2,54 @@ import React from 'react'; ...@@ -2,40 +2,54 @@ import React from 'react';
2 import palette from '../../lib/styles/palette'; 2 import palette from '../../lib/styles/palette';
3 import BJIDForm from './BJIDForm'; 3 import BJIDForm from './BJIDForm';
4 import SlackForm from './SlackForm'; 4 import SlackForm from './SlackForm';
5 +import GoalNumForm from './GoalNumForm';
5 import { makeStyles } from '@material-ui/core/styles'; 6 import { makeStyles } from '@material-ui/core/styles';
6 import Paper from '@material-ui/core/Paper'; 7 import Paper from '@material-ui/core/Paper';
7 import Grid from '@material-ui/core/Grid'; 8 import Grid from '@material-ui/core/Grid';
9 +import CircularProgress from '@material-ui/core/CircularProgress';
10 +import styled from 'styled-components';
8 11
9 const useStyles = makeStyles((theme) => ({ 12 const useStyles = makeStyles((theme) => ({
10 root: { 13 root: {
11 flexGrow: 1, 14 flexGrow: 1,
12 background: palette.gray[2], 15 background: palette.gray[2],
16 + padding: theme.spacing(8),
13 }, 17 },
14 paper: { 18 paper: {
19 + padding: theme.spacing(8),
15 margin: 'auto', 20 margin: 'auto',
16 textAlign: 'center', 21 textAlign: 'center',
17 - padding: 30,
18 }, 22 },
19 })); 23 }));
20 24
25 +const LoadingParentStyle = styled.div`
26 + display: flex;
27 + flex-direction: column;
28 + justify-content: center;
29 + align-items: center;
30 + padding-top: 20px;
31 +`;
32 +
21 const SettingForm = ({ 33 const SettingForm = ({
22 onChange, 34 onChange,
23 onBJIDSubmit, 35 onBJIDSubmit,
24 onSlackURLSubmit, 36 onSlackURLSubmit,
25 profile, 37 profile,
26 onSyncBJIDSubmit, 38 onSyncBJIDSubmit,
39 + onGoalNumSubmit,
40 + isLoading,
27 }) => { 41 }) => {
28 const classes = useStyles(); 42 const classes = useStyles();
29 - return ( 43 + return isLoading ? (
44 + <LoadingParentStyle>
45 + <CircularProgress className={classes.loading} />
46 + </LoadingParentStyle>
47 + ) : (
30 <div className={classes.root}> 48 <div className={classes.root}>
31 - <Grid container spacing={3}> 49 + <Grid container spacing={5}>
32 - <Grid item xs={12}> 50 + <Grid container item xs={6}>
33 - <Paper className={classes.paper}>
34 - <h3>{profile.username}</h3>
35 - </Paper>
36 - </Grid>
37 - <Grid container item xs={12}>
38 <Paper className={classes.paper} elevation={3}> 51 <Paper className={classes.paper} elevation={3}>
52 + <h1>백준 아이디</h1>
39 <BJIDForm 53 <BJIDForm
40 profile={profile} 54 profile={profile}
41 onChange={onChange} 55 onChange={onChange}
...@@ -45,8 +59,9 @@ const SettingForm = ({ ...@@ -45,8 +59,9 @@ const SettingForm = ({
45 </Paper> 59 </Paper>
46 </Grid> 60 </Grid>
47 61
48 - <Grid container item xs={12}> 62 + <Grid container item xs={6}>
49 <Paper className={classes.paper} elevation={3}> 63 <Paper className={classes.paper} elevation={3}>
64 + <h1>슬랙 Hook URL</h1>
50 <SlackForm 65 <SlackForm
51 profile={profile} 66 profile={profile}
52 onChange={onChange} 67 onChange={onChange}
...@@ -54,6 +69,17 @@ const SettingForm = ({ ...@@ -54,6 +69,17 @@ const SettingForm = ({
54 /> 69 />
55 </Paper> 70 </Paper>
56 </Grid> 71 </Grid>
72 +
73 + <Grid container item xs={6}>
74 + <Paper className={classes.paper} elevation={3}>
75 + <h1>일일 목표</h1>
76 + <GoalNumForm
77 + profile={profile}
78 + onChange={onChange}
79 + onGoalNumSubmit={onGoalNumSubmit}
80 + />
81 + </Paper>
82 + </Grid>
57 </Grid> 83 </Grid>
58 </div> 84 </div>
59 ); 85 );
......
...@@ -10,13 +10,16 @@ const useStyles = makeStyles((theme) => ({ ...@@ -10,13 +10,16 @@ const useStyles = makeStyles((theme) => ({
10 margin: theme.spacing(1), 10 margin: theme.spacing(1),
11 }, 11 },
12 }, 12 },
13 + button: {
14 + margin: theme.spacing(1),
15 + },
13 })); 16 }));
14 17
15 const SlackForm = ({ onChange, profile, onSlackURLSubmit }) => { 18 const SlackForm = ({ onChange, profile, onSlackURLSubmit }) => {
16 const classes = useStyles(); 19 const classes = useStyles();
17 return ( 20 return (
18 <div> 21 <div>
19 - <form onSubmit={onSlackURLSubmit}> 22 + <form>
20 <TextField 23 <TextField
21 name="slackWebHookURL" 24 name="slackWebHookURL"
22 onChange={onChange} 25 onChange={onChange}
...@@ -24,10 +27,16 @@ const SlackForm = ({ onChange, profile, onSlackURLSubmit }) => { ...@@ -24,10 +27,16 @@ const SlackForm = ({ onChange, profile, onSlackURLSubmit }) => {
24 placeholder="슬랙 Webhook URL" 27 placeholder="슬랙 Webhook URL"
25 label="슬랙 Webhook URL" 28 label="슬랙 Webhook URL"
26 /> 29 />
27 - <Button variant="outlined" type="submit"> 30 + </form>
31 + <Button
32 + className={classes.button}
33 + onClick={onSlackURLSubmit}
34 + variant="outlined"
35 + type="submit"
36 + color="primary"
37 + >
28 등록 38 등록
29 </Button> 39 </Button>
30 - </form>
31 </div> 40 </div>
32 ); 41 );
33 }; 42 };
......
...@@ -2,21 +2,35 @@ import React, { useEffect } from 'react'; ...@@ -2,21 +2,35 @@ import React, { useEffect } from 'react';
2 import { useDispatch, useSelector } from 'react-redux'; 2 import { useDispatch, useSelector } from 'react-redux';
3 import { withRouter } from 'react-router-dom'; 3 import { withRouter } from 'react-router-dom';
4 import HomeForm from '../../components/home/HomeForm'; 4 import HomeForm from '../../components/home/HomeForm';
5 -import { getPROFILE } from '../../modules/profile'; 5 +import { getPROFILE, initializeProfile } from '../../modules/profile';
6 -import { analyzeBJ } from '../../lib/util/analyzeBJ';
7 const HomeContainer = ({ history }) => { 6 const HomeContainer = ({ history }) => {
8 const dispatch = useDispatch(); 7 const dispatch = useDispatch();
9 const { user, profile } = useSelector(({ user, profile }) => ({ 8 const { user, profile } = useSelector(({ user, profile }) => ({
10 user: user.user, 9 user: user.user,
11 profile: profile, 10 profile: profile,
12 })); 11 }));
13 - useEffect(() => {}, [profile.solvedBJ]); 12 +
13 + useEffect(() => {
14 + if (!user) {
15 + alert('로그인이 필요합니다 ');
16 + history.push('/login');
17 + } else {
18 + let username = user.username;
19 + dispatch(getPROFILE({ username }));
20 + return () => {
21 + dispatch(initializeProfile());
22 + };
23 + }
24 + }, [dispatch, user, history]);
25 + useEffect(() => {
26 + console.log(profile);
27 + }, [profile]);
14 useEffect(() => { 28 useEffect(() => {
15 if (user) { 29 if (user) {
16 let username = user.username; 30 let username = user.username;
17 dispatch(getPROFILE({ username })); 31 dispatch(getPROFILE({ username }));
18 } 32 }
19 }, [dispatch, user]); 33 }, [dispatch, user]);
20 - return <HomeForm />; 34 + return <HomeForm PSdata={profile.solvedBJ_date} goalNum={profile.goalNum} />;
21 }; 35 };
22 export default withRouter(HomeContainer); 36 export default withRouter(HomeContainer);
......
1 -import React, { useEffect } from 'react'; 1 +import React, { useEffect, useState } from 'react';
2 import { useDispatch, useSelector } from 'react-redux'; 2 import { useDispatch, useSelector } from 'react-redux';
3 +
3 import { withRouter } from 'react-router-dom'; 4 import { withRouter } from 'react-router-dom';
4 import { 5 import {
5 changeField, 6 changeField,
...@@ -8,15 +9,21 @@ import { ...@@ -8,15 +9,21 @@ import {
8 syncBJID, 9 syncBJID,
9 initializeProfile, 10 initializeProfile,
10 setSLACK, 11 setSLACK,
12 + setGOALNUM,
11 } from '../../modules/profile'; 13 } from '../../modules/profile';
12 import SettingForm from '../../components/setting/SettingForm'; 14 import SettingForm from '../../components/setting/SettingForm';
13 15
14 const SettingContainer = ({ history }) => { 16 const SettingContainer = ({ history }) => {
17 + const [isLoading, setLoading] = useState(false);
18 +
15 const dispatch = useDispatch(); 19 const dispatch = useDispatch();
16 - const { user, profile } = useSelector(({ user, profile }) => ({ 20 + const { user, profile, loading } = useSelector(
21 + ({ user, profile, loading }) => ({
17 user: user.user, 22 user: user.user,
18 profile: profile, 23 profile: profile,
19 - })); 24 + loading: loading,
25 + }),
26 + );
20 27
21 const onChange = (e) => { 28 const onChange = (e) => {
22 const { value, name } = e.target; 29 const { value, name } = e.target;
...@@ -33,6 +40,13 @@ const SettingContainer = ({ history }) => { ...@@ -33,6 +40,13 @@ const SettingContainer = ({ history }) => {
33 let username = profile.username; 40 let username = profile.username;
34 dispatch(syncBJID({ username })); 41 dispatch(syncBJID({ username }));
35 }; 42 };
43 +
44 + const onGoalNumSubmit = (e) => {
45 + e.preventDefault();
46 + let username = profile.username;
47 + let goalNum = profile.goalNum;
48 + dispatch(setGOALNUM({ username, goalNum }));
49 + };
36 const onSlackURLSubmit = (e) => { 50 const onSlackURLSubmit = (e) => {
37 e.preventDefault(); 51 e.preventDefault();
38 let username = profile.username; 52 let username = profile.username;
...@@ -51,7 +65,7 @@ const SettingContainer = ({ history }) => { ...@@ -51,7 +65,7 @@ const SettingContainer = ({ history }) => {
51 useEffect(() => { 65 useEffect(() => {
52 if (!user) { 66 if (!user) {
53 alert('로그인이 필요합니다 '); 67 alert('로그인이 필요합니다 ');
54 - history.push('/'); 68 + history.push('/login');
55 } else { 69 } else {
56 let username = user.username; 70 let username = user.username;
57 dispatch(getPROFILE({ username })); 71 dispatch(getPROFILE({ username }));
...@@ -60,16 +74,27 @@ const SettingContainer = ({ history }) => { ...@@ -60,16 +74,27 @@ const SettingContainer = ({ history }) => {
60 }; 74 };
61 } 75 }
62 }, [dispatch, user, history]); 76 }, [dispatch, user, history]);
77 + useEffect(() => {
78 + if (loading['profile/SYNC_BJID'] == true) {
79 + setLoading(true);
80 + } else {
81 + setLoading(false);
82 + }
83 + }, [dispatch, loading]);
63 84
64 return ( 85 return (
86 + <div>
65 <SettingForm 87 <SettingForm
66 type="setting" 88 type="setting"
67 onChange={onChange} 89 onChange={onChange}
68 onBJIDSubmit={onBJIDSubmit} 90 onBJIDSubmit={onBJIDSubmit}
69 onSyncBJIDSubmit={onSyncBJIDSubmit} 91 onSyncBJIDSubmit={onSyncBJIDSubmit}
70 onSlackURLSubmit={onSlackURLSubmit} 92 onSlackURLSubmit={onSlackURLSubmit}
93 + onGoalNumSubmit={onGoalNumSubmit}
71 profile={profile} 94 profile={profile}
95 + isLoading={isLoading}
72 ></SettingForm> 96 ></SettingForm>
97 + </div>
73 ); 98 );
74 }; 99 };
75 100
......
...@@ -17,6 +17,11 @@ const [ ...@@ -17,6 +17,11 @@ const [
17 SET_SLACK_FAILURE, 17 SET_SLACK_FAILURE,
18 ] = createRequestActionTypes('/profile/SET_SLACK'); 18 ] = createRequestActionTypes('/profile/SET_SLACK');
19 const [ 19 const [
20 + SET_GOALNUM,
21 + SET_GOALNUM_SUCCESS,
22 + SET_GOALNUM_FAILURE,
23 +] = createRequestActionTypes('/profile/SET_GOALNUM');
24 +const [
20 GET_PROFILE, 25 GET_PROFILE,
21 GET_PROFILE_SUCCESS, 26 GET_PROFILE_SUCCESS,
22 GET_PROFILE_FAILURE, 27 GET_PROFILE_FAILURE,
...@@ -31,6 +36,7 @@ export const initializeProfile = createAction(INITIALIZE); ...@@ -31,6 +36,7 @@ export const initializeProfile = createAction(INITIALIZE);
31 export const syncBJID = createAction(SYNC_BJID, ({ username }) => ({ 36 export const syncBJID = createAction(SYNC_BJID, ({ username }) => ({
32 username, 37 username,
33 })); 38 }));
39 +
34 export const setSLACK = createAction( 40 export const setSLACK = createAction(
35 SET_SLACK, 41 SET_SLACK,
36 ({ username, slackWebHookURL }) => ({ 42 ({ username, slackWebHookURL }) => ({
...@@ -38,6 +44,14 @@ export const setSLACK = createAction( ...@@ -38,6 +44,14 @@ export const setSLACK = createAction(
38 slackWebHookURL, 44 slackWebHookURL,
39 }), 45 }),
40 ); 46 );
47 +
48 +export const setGOALNUM = createAction(
49 + SET_GOALNUM,
50 + ({ username, goalNum }) => ({
51 + username,
52 + goalNum,
53 + }),
54 +);
41 export const setBJID = createAction(SET_BJID, ({ username, userBJID }) => ({ 55 export const setBJID = createAction(SET_BJID, ({ username, userBJID }) => ({
42 username, 56 username,
43 userBJID, 57 userBJID,
...@@ -58,16 +72,21 @@ const initialState = { ...@@ -58,16 +72,21 @@ const initialState = {
58 friendList: [], 72 friendList: [],
59 profileError: '', 73 profileError: '',
60 slackWebHookURL: '', 74 slackWebHookURL: '',
75 + solvedBJ_date: '',
76 + goalNum: '',
61 }; 77 };
62 const getPROFILESaga = createRequestSaga(GET_PROFILE, profileAPI.getPROFILE); 78 const getPROFILESaga = createRequestSaga(GET_PROFILE, profileAPI.getPROFILE);
63 const setBJIDSaga = createRequestSaga(SET_BJID, profileAPI.setBJID); 79 const setBJIDSaga = createRequestSaga(SET_BJID, profileAPI.setBJID);
64 const setSLACKSaga = createRequestSaga(SET_SLACK, profileAPI.setPROFILE); 80 const setSLACKSaga = createRequestSaga(SET_SLACK, profileAPI.setPROFILE);
81 +const setGOALNUMSaga = createRequestSaga(SET_GOALNUM, profileAPI.setPROFILE);
65 const syncBJIDSaga = createRequestSaga(SYNC_BJID, profileAPI.syncBJ); 82 const syncBJIDSaga = createRequestSaga(SYNC_BJID, profileAPI.syncBJ);
83 +
66 export function* profileSaga() { 84 export function* profileSaga() {
67 yield takeLatest(SET_BJID, setBJIDSaga); 85 yield takeLatest(SET_BJID, setBJIDSaga);
68 yield takeLatest(GET_PROFILE, getPROFILESaga); 86 yield takeLatest(GET_PROFILE, getPROFILESaga);
69 yield takeLatest(SYNC_BJID, syncBJIDSaga); 87 yield takeLatest(SYNC_BJID, syncBJIDSaga);
70 yield takeLatest(SET_SLACK, setSLACKSaga); 88 yield takeLatest(SET_SLACK, setSLACKSaga);
89 + yield takeLatest(SET_GOALNUM, setGOALNUMSaga);
71 } 90 }
72 91
73 export default handleActions( 92 export default handleActions(
...@@ -80,7 +99,15 @@ export default handleActions( ...@@ -80,7 +99,15 @@ export default handleActions(
80 [GET_PROFILE_SUCCESS]: ( 99 [GET_PROFILE_SUCCESS]: (
81 state, 100 state,
82 { 101 {
83 - payload: { username, userBJID, solvedBJ, friendList, slackWebHookURL }, 102 + payload: {
103 + username,
104 + userBJID,
105 + solvedBJ,
106 + friendList,
107 + slackWebHookURL,
108 + solvedBJ_date,
109 + goalNum,
110 + },
84 }, 111 },
85 ) => ({ 112 ) => ({
86 ...state, 113 ...state,
...@@ -90,6 +117,8 @@ export default handleActions( ...@@ -90,6 +117,8 @@ export default handleActions(
90 friendList: friendList, 117 friendList: friendList,
91 profileError: null, 118 profileError: null,
92 slackWebHookURL: slackWebHookURL, 119 slackWebHookURL: slackWebHookURL,
120 + solvedBJ_date: solvedBJ_date,
121 + goalNum: goalNum,
93 }), 122 }),
94 [GET_PROFILE_FAILURE]: (state, { payload: error }) => ({ 123 [GET_PROFILE_FAILURE]: (state, { payload: error }) => ({
95 ...state, 124 ...state,
...@@ -114,6 +143,14 @@ export default handleActions( ...@@ -114,6 +143,14 @@ export default handleActions(
114 ...state, 143 ...state,
115 profileError: error, 144 profileError: error,
116 }), 145 }),
146 + [SET_GOALNUM_SUCCESS]: (state, { payload: { goalNum } }) => ({
147 + ...state,
148 + goalNum: goalNum,
149 + }),
150 + [SET_GOALNUM_FAILURE]: (state, { payload: error }) => ({
151 + ...state,
152 + profileError: error,
153 + }),
117 [SYNC_BJID_SUCCESS]: (state, { payload: { solvedBJ } }) => ({ 154 [SYNC_BJID_SUCCESS]: (state, { payload: { solvedBJ } }) => ({
118 ...state, 155 ...state,
119 solvedBJ, 156 solvedBJ,
......
1 -# Jaksimsamil API Documentation
2 -
3 -## Overview
4 -
5 -- TBA
6 -
7 -## URL
8 -
9 -- TBA
10 -
11 -## Usage
12 -
13 -- TBA
14 -
15 -## Example
16 -
17 -- TBA
18 -
19 -## API Table
20 -
21 -| group | description | method | URL | Detail | Auth |
22 -| ------- | --------------------------- | --------- | ------------------------ | -------- | --------- |
23 -| user | 유저 등록 | POST | api/user | 바로가기 | JWT Token |
24 -| user | 유저 삭제 | DELETE | api/user:id | 바로가기 | JWT Token |
25 -| user | 특정 유저 조회 | GET | api/user:id | 바로가기 | None |
26 -| user | 전체 유저 조회 | GET | api/user | 바로가기 | JWT Token |
27 -| friend | 유저 친구 등록 | POST | api/friend | 바로가기 | JWT Token |
28 -| friend | 유저의 친구 조회 | GET | api/friend:id | 바로가기 | None |
29 -| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | 바로가기 | None |
30 -| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | 바로가기 | None |
31 -| profile | 유저 정보 수정 | POST | api/profile/setprofile | 바로가기 | JWT TOKEN |
32 -| profile | 유저 정보 받아오기 | POST | api/profile/getprofile | 바로가기 | JWT |
33 -| profile | 추천 문제 조회 | GET | api/profile/recommend:id | 바로가기 | None |
34 -| notify | 슬랙 메시지 전송 요청 | POST | api/notify/ |
35 -| slack | 바로가기 | Jwt Token |
36 -| auth | 로그인 | POST | api/auth/login | 바로가기 | None |
37 -| auth | 로그아웃 | POST | api/auth/logout | 바로가기 | JWT Token |
38 -| auth | 회원가입 | POST | api/auth/register | 바로가기 | None |
39 -| auth | 로그인 확인 | GET | api/auth/check | 바로가기 | None |
1 +# Jaksimsamil Server Documentation
2 +
3 +## Overview
4 +
5 +- KOA 프레임워크 기반의 REST-API로 동작합니다.
6 +- API 문서는 아래를 참고해주세요.
7 +
8 +## Usage
9 +
10 +- Starting Server
11 +
12 +```
13 +npm install
14 +npm update
15 +node index.js
16 +```
17 +
18 +## Example
19 +
20 +```
21 +POST http://facerain.dcom.club/profile/getprofile
22 +{
23 + username: 'syw5141',
24 +}
25 +```
26 +
27 +## API Table
28 +
29 +| group | description | method | URL | Detail | Auth |
30 +| ------- | -------------------------------------- | ------ | ----------------------- | -------------------------------------- | --------- |
31 +| profile | 유저가 푼 문제 조회(백준) | GET | api/profile/solvedBJ:id | [바로가기](/src/api/profile/README.md) | None |
32 +| profile | 유저가 푼 문제 동기화(백준) | PATCH | api/profile/syncBJ | [바로가기](/src/api/profile/README.md) | None |
33 +| profile | 유저 정보 수정 | POST | api/profile/setprofile | [바로가기](/src/api/profile/README.md) | JWT TOKEN |
34 +| profile | 유저 정보 받아오기 | POST | api/profile/getprofile | [바로가기](/src/api/profile/README.md) | JWT |
35 +| profile | 추천 문제 조회 | POST | api/profile/recommend | [바로가기](/src/api/profile/README.md) | None |
36 +| profile | 친구 추가 | POST | /api/profile/addfriend | [바로가기](/src/api/profile/README.md) | JWT TOKEN |
37 +| notify | 슬랙 메시지 전송 요청 (목표 성취 여부) | POST | api/notify/goal | [바로가기](/src/api/notify/README.md) | Jwt Token |
38 +| notify | 슬랙 메시지 전송 요청 (문제 추천) | POST | api/notify/recommend | [바로가기](/src/api/notify/README.md) | None |
39 +| auth | 로그인 | POST | api/auth/login | [바로가기](/src/api/auth/README.md) | None |
40 +| auth | 로그아웃 | POST | api/auth/logout | [바로가기](/src/api/auth/README.md) | JWT Token |
41 +| auth | 회원가입 | POST | api/auth/register | [바로가기](/src/api/auth/README.md) | None |
42 +| auth | 로그인 확인 | GET | api/auth/check | [바로가기](/src/api/auth/README.md) | None |
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 "license": "MIT", 5 "license": "MIT",
6 "dependencies": { 6 "dependencies": {
7 "axios": "^0.19.2", 7 "axios": "^0.19.2",
8 - "bcrypt": "^4.0.1", 8 + "bcrypt": "^3.0.0",
9 "body-parser": "^1.19.0", 9 "body-parser": "^1.19.0",
10 "cheerio": "^1.0.0-rc.3", 10 "cheerio": "^1.0.0-rc.3",
11 "cookie-parser": "^1.4.5", 11 "cookie-parser": "^1.4.5",
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
15 "iconv": "^3.0.0", 15 "iconv": "^3.0.0",
16 "joi": "^14.3.1", 16 "joi": "^14.3.1",
17 "jsonwebtoken": "^8.5.1", 17 "jsonwebtoken": "^8.5.1",
18 - "koa": "^2.12.0", 18 + "koa": "^2.13.0",
19 "koa-bodyparser": "^4.3.0", 19 "koa-bodyparser": "^4.3.0",
20 "koa-morgan": "^1.0.1", 20 "koa-morgan": "^1.0.1",
21 "koa-router": "^9.0.1", 21 "koa-router": "^9.0.1",
22 - "mongoose": "^5.9.17", 22 + "mongoose": "^5.9.20",
23 "morgan": "^1.10.0", 23 "morgan": "^1.10.0",
24 "node-schedule": "^1.3.2", 24 "node-schedule": "^1.3.2",
25 "path": "^0.12.7", 25 "path": "^0.12.7",
...@@ -29,11 +29,11 @@ ...@@ -29,11 +29,11 @@
29 }, 29 },
30 "devDependencies": { 30 "devDependencies": {
31 "babel-eslint": "^10.1.0", 31 "babel-eslint": "^10.1.0",
32 - "eslint": "^7.1.0", 32 + "eslint": "^7.3.1",
33 "nodemon": "^2.0.4" 33 "nodemon": "^2.0.4"
34 }, 34 },
35 "scripts": { 35 "scripts": {
36 - "start": "node src", 36 + "start": "node ./index.js",
37 "start:dev": "nodemon --watch src/ src/index.js" 37 "start:dev": "nodemon --watch src/ src/index.js"
38 } 38 }
39 } 39 }
......
...@@ -3,7 +3,7 @@ const api = new Router(); ...@@ -3,7 +3,7 @@ const api = new Router();
3 3
4 const auth = require("./auth"); 4 const auth = require("./auth");
5 const friend = require("./friend"); 5 const friend = require("./friend");
6 -const notify = require("./profile"); 6 +const notify = require("./notify");
7 const user = require("./user"); 7 const user = require("./user");
8 const profile = require("./profile"); 8 const profile = require("./profile");
9 9
......
1 const Router = require("koa-router"); 1 const Router = require("koa-router");
2 const notify = new Router(); 2 const notify = new Router();
3 - 3 +const slackCtrl = require("./slack.ctrl");
4 -notify.post("/slack"); 4 +notify.post("/slack/goal", slackCtrl.slackGoal);
5 +notify.post("/slack/recommend", slackCtrl.slackRecommend);
5 6
6 module.exports = notify; 7 module.exports = notify;
......
1 +const Profile = require("../../models/profile");
2 +const sendSlack = require("../../util/sendSlack");
3 +const problem_set = require("../../data/problem_set");
4 +const compareBJ = require("../../util/compareBJ");
5 +/*
6 +POST api/notify/slack/goal
7 +{
8 + username: "username"
9 +}
10 +*/
11 +exports.slackGoal = async (ctx) => {
12 + try {
13 + const { username } = ctx.request.body;
14 +
15 + const profile = await Profile.findByUsername(username);
16 + if (!profile) {
17 + ctx.status = 401;
18 + return;
19 + }
20 + let slackURL = profile.getslackURL();
21 + if (!slackURL) {
22 + ctx.status = 401;
23 + return;
24 + }
25 + let goalNum = profile.getgoalNum();
26 + let todayNum = profile.getTodaySovled();
27 + let message = "";
28 + if (goalNum < todayNum) {
29 + message =
30 + "오늘의 목표 " +
31 + goalNum +
32 + "문제 중 " +
33 + todayNum +
34 + "문제를 풀었습니다." +
35 + "\n" +
36 + "잘하셨습니다!";
37 + } else {
38 + message =
39 + "오늘의 목표 " +
40 + goalNum +
41 + "문제 중 " +
42 + todayNum +
43 + "문제를 풀었습니다." +
44 + "\n" +
45 + "분발하세요!";
46 + }
47 +
48 + sendSlack.send(message, slackURL);
49 + } catch (e) {
50 + ctx.throw(500, e);
51 + }
52 +};
53 +
54 +/*
55 +POST api/notify/slack/recommend
56 +{
57 + username: "username"
58 +}
59 +*/
60 +exports.slackRecommend = async (ctx) => {
61 + try {
62 + console.log("1");
63 + const { username } = ctx.request.body;
64 +
65 + const profile = await Profile.findByUsername(username);
66 + if (!profile) {
67 + ctx.status = 401;
68 + return;
69 + }
70 + let slackURL = profile.getslackURL();
71 + if (!slackURL) {
72 + ctx.status = 401;
73 + return;
74 + }
75 + let unsolved_data = compareBJ.compareBJ(
76 + profile.getBJdata(),
77 + problem_set.problem_set
78 + );
79 + let recommendData = compareBJ.randomItem(unsolved_data);
80 +
81 + if (!recommendData) {
82 + ctx.status = 401;
83 + return;
84 + }
85 + let message =
86 + "오늘의 추천 문제는 " +
87 + recommendData.problem_number +
88 + "번 " +
89 + " <https://www.boj.kr/" +
90 + recommendData.problem_number +
91 + "|" +
92 + recommendData.problem_title +
93 + ">" +
94 + " 입니다.";
95 + sendSlack.send(message, slackURL);
96 + } catch (e) {
97 + ctx.throw(500, e);
98 + }
99 +};
1 const Router = require("koa-router"); 1 const Router = require("koa-router");
2 const profile = new Router(); 2 const profile = new Router();
3 const profileCtrl = require("./profile.ctrl"); 3 const profileCtrl = require("./profile.ctrl");
4 -
5 profile.post("/solved:id"); 4 profile.post("/solved:id");
6 profile.get("/solvednum:id"); 5 profile.get("/solvednum:id");
7 -profile.get("/recommendps:id"); 6 +profile.post("/recommend", profileCtrl.recommend);
8 profile.patch("/syncBJ", profileCtrl.syncBJ); 7 profile.patch("/syncBJ", profileCtrl.syncBJ);
9 profile.post("/setprofile", profileCtrl.setProfile); 8 profile.post("/setprofile", profileCtrl.setProfile);
10 profile.post("/getprofile", profileCtrl.getProfile); 9 profile.post("/getprofile", profileCtrl.getProfile);
......
...@@ -2,6 +2,9 @@ const Profile = require("../../models/profile"); ...@@ -2,6 +2,9 @@ const Profile = require("../../models/profile");
2 const mongoose = require("mongoose"); 2 const mongoose = require("mongoose");
3 const getBJ = require("../../util/getBJ"); 3 const getBJ = require("../../util/getBJ");
4 const Joi = require("joi"); 4 const Joi = require("joi");
5 +const analyzeBJ = require("../../util/analyzeBJ");
6 +const compareBJ = require("../../util/compareBJ");
7 +const problem_set = require("../../data/problem_set");
5 8
6 const { ObjectId } = mongoose.Types; 9 const { ObjectId } = mongoose.Types;
7 10
...@@ -47,7 +50,7 @@ exports.setProfile = async (ctx) => { ...@@ -47,7 +50,7 @@ exports.setProfile = async (ctx) => {
47 //freindList: Joi.array().items(Joi.string()), 50 //freindList: Joi.array().items(Joi.string()),
48 }) 51 })
49 .unknown(); 52 .unknown();
50 - 53 + console.log(ctx.request.body);
51 const result = Joi.validate(ctx.request.body, schema); 54 const result = Joi.validate(ctx.request.body, schema);
52 if (result.error) { 55 if (result.error) {
53 ctx.status = 400; 56 ctx.status = 400;
...@@ -95,9 +98,10 @@ exports.syncBJ = async function (ctx) { ...@@ -95,9 +98,10 @@ exports.syncBJ = async function (ctx) {
95 } 98 }
96 const BJID = await profile.getBJID(); 99 const BJID = await profile.getBJID();
97 let BJdata = await getBJ.getBJ(BJID); 100 let BJdata = await getBJ.getBJ(BJID);
101 + let BJdata_date = await analyzeBJ.analyzeBJ(BJdata);
98 const updateprofile = await Profile.findOneAndUpdate( 102 const updateprofile = await Profile.findOneAndUpdate(
99 { username: username }, 103 { username: username },
100 - { solvedBJ: BJdata }, 104 + { solvedBJ: BJdata, solvedBJ_date: BJdata_date },
101 { new: true } 105 { new: true }
102 ).exec(); 106 ).exec();
103 ctx.body = updateprofile; 107 ctx.body = updateprofile;
...@@ -105,3 +109,33 @@ exports.syncBJ = async function (ctx) { ...@@ -105,3 +109,33 @@ exports.syncBJ = async function (ctx) {
105 ctx.throw(500, e); 109 ctx.throw(500, e);
106 } 110 }
107 }; 111 };
112 +
113 +/*
114 +POST /api/proflie/recommend
115 +{
116 + username: 'userid'
117 +}
118 + */
119 +exports.recommend = async (ctx) => {
120 + const { username } = ctx.request.body;
121 +
122 + if (!username) {
123 + ctx.status = 401;
124 + return;
125 + }
126 + try {
127 + const profile = await Profile.findByUsername(username);
128 + if (!profile) {
129 + ctx.status = 401;
130 + return;
131 + }
132 + let unsolved_data = compareBJ.compareBJ(
133 + profile.getBJdata(),
134 + problem_set.problem_set
135 + );
136 + ctx.body = compareBJ.randomItem(unsolved_data);
137 + //데이터가 비었을 떄 예외처리 필요
138 + } catch (e) {
139 + ctx.throw(500, e);
140 + }
141 +};
......
This diff could not be displayed because it is too large.
1 +const mongoose = require("mongoose");
2 +
3 +const { Schema } = mongoose;
4 +
5 +const GroupSchema = new Schema({
6 + members: { type: [String] },
7 +});
8 +
9 +const ChallengeSchema = new Schema({
10 + challengeName: { type: String, required: true },
11 + startDate: { type: Object, required: true },
12 + endDate: { type: Object, required: true },
13 + durationPerSession: { type: String, required: true }, // '1d' means one day per session, '2w' means 2 weeks per session, '3m' means 3 months per session.
14 + goalPerSession: { type: Number, required: true }, // number of problems for one session
15 + groups: { type: [GroupSchema], required: true }, // groups attending challenge, group of only one member supposed to be single
16 +});
17 +
18 +ChallengeSchema.statics.findByChallengeName = function (challengeName) {
19 + return this.findOne({ challengeName: challengeName });
20 +};
21 +
22 +ChallengeSchema.methods.addNewGroup = function (group) {
23 + this.groups.push(group);
24 + return this.save();
25 +};
26 +
27 +ChallengeSchema.methods.removeGroup = function (group_id) {
28 + const idx = this.groups.findIndex((item) => item._id === group_id);
29 + this.groups.splice(idx, 1);
30 + return this.save();
31 +};
32 +
33 +ChallengeSchema.methods.getChallengeName = function () {
34 + return this.challengeName;
35 +};
36 +
37 +ChallengeSchema.methods.getStartDate = function () {
38 + return this.startDate;
39 +};
40 +
41 +ChallengeSchema.methods.getEndDate = function () {
42 + return this.endDate;
43 +};
44 +
45 +ChallengeSchema.methods.getDurationPerSession = function () {
46 + return this.durationPerSession;
47 +};
48 +
49 +ChallengeSchema.methods.getGoalPerSession = function () {
50 + return this.goalPerSession;
51 +};
52 +
53 +ChallengeSchema.methods.getGroups = function () {
54 + return this.groups;
55 +};
56 +
57 +ChallengeSchema.methods.serialize = function () {
58 + return this.toJSON();
59 +};
60 +
61 +const Challenge = mongoose.model("Challenge", ChallengeSchema);
62 +module.exports = Challenge;
1 +const mongoose=require('mongoose');
2 +
3 +const {Schema}=mongoose;
4 +
5 +const ProblemSchema=new Schema({
6 + problemNum: {type: Number, required: true, unique: true},
7 + problemTitle: {type: String, required: true},
8 + solvedacLevel: {type: Number},
9 + sumbitNum: {type: Number, required: true},
10 + correctNum: {type: Number, required: true},
11 + category: {type:[String]}
12 +});
13 +
14 +ProblemSchema.statics.findByProblemNum=function(problemNum){
15 + return this.findOne({problemNum:problemNum});
16 +}
17 +
18 +ProblemSchema.methods.addCategory=function(category){
19 + this.category.push(category);
20 + return this.save();
21 +}
22 +
23 +ProblemSchema.methods.removeCategory=function(category){
24 + const idx=this.category.findIndex(item=>item===category);
25 + this.splice(idx,1);
26 + return this.save();
27 +}
28 +
29 +ProblemSchema.methods.getProblemNum=function(){
30 + return this.problemNum;
31 +}
32 +
33 +ProblemSchema.methods.getProblemTitle=function(){
34 + return this.problemTitle;
35 +}
36 +
37 +ProblemSchema.methods.getSolvedacLevel=function(){
38 + return this.solvedacLevel;
39 +}
40 +
41 +ProblemSchema.methods.getSumbitNum=function(){
42 + return this.sumbitNum;
43 +}
44 +
45 +ProblemSchema.methods.getCorrectNum=function(){
46 + return this.correctNum;
47 +}
48 +
49 +ProblemSchema.methods.getCategory=function(){
50 + return this.category;
51 +}
52 +
53 +ProblemSchema.methods.serialize=function(){
54 + return this.toJSON();
55 +}
56 +
57 +const Problem=mongoose.model('Problem',ProblemSchema);
58 +module.exports=Problem;
...\ No newline at end of file ...\ No newline at end of file
...@@ -6,8 +6,10 @@ const ProfileSchema = new Schema({ ...@@ -6,8 +6,10 @@ const ProfileSchema = new Schema({
6 username: { type: String, required: true, unique: true }, 6 username: { type: String, required: true, unique: true },
7 userBJID: String, 7 userBJID: String,
8 solvedBJ: Object, 8 solvedBJ: Object,
9 + solvedBJ_date: Object,
9 friendList: [String], 10 friendList: [String],
10 slackWebHookURL: String, 11 slackWebHookURL: String,
12 + goalNum: Number,
11 }); 13 });
12 ProfileSchema.statics.findByUsername = function (username) { 14 ProfileSchema.statics.findByUsername = function (username) {
13 return this.findOne({ username }); 15 return this.findOne({ username });
...@@ -15,6 +17,20 @@ ProfileSchema.statics.findByUsername = function (username) { ...@@ -15,6 +17,20 @@ ProfileSchema.statics.findByUsername = function (username) {
15 ProfileSchema.methods.getBJID = function () { 17 ProfileSchema.methods.getBJID = function () {
16 return this.userBJID; 18 return this.userBJID;
17 }; 19 };
20 +ProfileSchema.methods.getBJdata = function () {
21 + return this.solvedBJ;
22 +};
23 +ProfileSchema.methods.getslackURL = function () {
24 + return this.slackWebHookURL;
25 +};
26 +ProfileSchema.methods.getgoalNum = function () {
27 + return this.goalNum;
28 +};
29 +ProfileSchema.methods.getTodaySovled = function () {
30 + if (this.solvedBJ_date) {
31 + return this.solvedBJ_date.presentNum;
32 + }
33 +};
18 34
19 ProfileSchema.methods.serialize = function () { 35 ProfileSchema.methods.serialize = function () {
20 const data = this.toJSON(); 36 const data = this.toJSON();
......
1 -/* 1 +let moment = require("moment");
2 -2. 현재 날짜와의 차이 => 2 +const problem_set = require("../data/problem_set");
3 -3. 오늘 푼 문제 => 앞에서부터 순회하면서 데이트 같은거 찾기 3 +const compareBJ = require("./compareBJ");
4 -3. 최근 일주일간 푼 문제 수 => 앞에서부터 순회하면서 - 값이
5 -4. 추천 문제 => 정규 셋에서 없는거 찾기
6 -5. 날짜별로 묶기.
7 -데이터베이스에서 처리하자
8 -*/
9 -
10 -let moment = require('moment');
11 -
12 exports.analyzeBJ = function (solvedBJ) { 4 exports.analyzeBJ = function (solvedBJ) {
13 try { 5 try {
14 if (solvedBJ) { 6 if (solvedBJ) {
15 - console.log(solvedBJ[0]);
16 let presentDate = moment(); 7 let presentDate = moment();
17 - let presentDate_str = presentDate.format('YYYYMMDD'); 8 + let presentDate_str = presentDate.format("YYYYMMDD");
18 - let latestDate = moment(solvedBJ[0].solved_date, 'YYYYMMDD'); 9 + let latestDate = moment(solvedBJ[0].solved_date, "YYYYMMDD");
19 - let difflatest = presentDate.diff(latestDate, 'days'); 10 + let difflatest = presentDate.diff(latestDate, "days");
11 + let latestSolve = solvedBJ[0];
20 12
21 let solvedBJbyDATE = {}; 13 let solvedBJbyDATE = {};
22 for (let i = 0; i < solvedBJ.length; i++) { 14 for (let i = 0; i < solvedBJ.length; i++) {
...@@ -33,14 +25,46 @@ exports.analyzeBJ = function (solvedBJ) { ...@@ -33,14 +25,46 @@ exports.analyzeBJ = function (solvedBJ) {
33 presentDate_str in solvedBJbyDATE 25 presentDate_str in solvedBJbyDATE
34 ? solvedBJbyDATE[presentDate_str].length 26 ? solvedBJbyDATE[presentDate_str].length
35 : 0; 27 : 0;
28 +
29 + let weekNUM = 0;
30 + let monthNUM = 0;
31 + let totalNUM = 0;
32 + for (let i = 0; i < solvedBJ.length; i++) {
33 + let diffDate = presentDate.diff(
34 + moment(solvedBJ[i].solved_date, "YYYYMMDD"),
35 + "days"
36 + );
37 + if (diffDate <= 7) {
38 + weekNUM++;
39 + monthNUM++;
40 + totalNUM++;
41 + } else if (diffDate <= 31) {
42 + monthNUM++;
43 + totalNUM++;
44 + } else {
45 + totalNUM++;
46 + }
47 + }
48 +
49 + let unsolved_data = compareBJ.compareBJ(
50 + solvedBJ,
51 + problem_set.problem_set
52 + );
53 + let recommend_data = compareBJ.randomItem(unsolved_data);
54 +
36 let returnOBJ = { 55 let returnOBJ = {
37 - latestDate: latestDate.format('YYYYMMDD'), 56 + latestDate: latestDate.format("YYYYMMDD"),
38 difflatest: difflatest, 57 difflatest: difflatest,
39 latestNum: latestNum, 58 latestNum: latestNum,
40 presentNum: presentNum, 59 presentNum: presentNum,
60 + weekNum: weekNUM,
61 + monthNum: monthNUM,
62 + totalNum: totalNUM,
41 solvedBJbyDATE: solvedBJbyDATE, 63 solvedBJbyDATE: solvedBJbyDATE,
64 + latestSolve: latestSolve,
65 + recommend_data: recommend_data,
42 }; 66 };
43 - console.log(returnOBJ); 67 +
44 return returnOBJ; 68 return returnOBJ;
45 } 69 }
46 } catch (e) { 70 } catch (e) {
......
1 -/*
2 -집중을 해보자.
3 -새거와 데이터가 있다.
4 -데이터 기준으로 새거에 자료가 있으면 넘어가기
5 -없으면 새 배열에 추가
6 -키만 모아둔 리스트를 만들자.
7 -그렇게 해서
8 -반복은 새거 길이만큼
9 -데이터에 있으면 추가 X
10 -없으면 추가
11 -그렇게 반환
12 -*/
13 -
14 exports.compareBJ = function (solvedBJ_new, problem_set) { 1 exports.compareBJ = function (solvedBJ_new, problem_set) {
15 try { 2 try {
16 let new_obj = []; 3 let new_obj = [];
17 - for (let i = 0; i < solvedBJ.length; i++) { 4 +
18 - if (solvedBJ_new[i].problem_number in problem_set) { 5 + for (let i = 0; i < problem_set.length; i++) {
19 - new_obj.push(solvedBJ_new[i]); 6 + let found = false;
7 + for (let j = 0; j < solvedBJ_new.length; j++) {
8 + if (solvedBJ_new[j].problem_number == problem_set[i].problem_number) {
9 + found = true;
10 + break;
11 + }
20 } 12 }
13 + if (!found) {
14 + new_obj.push(problem_set[i]);
21 } 15 }
22 - console.log(new_obj); 16 + }
17 + return new_obj;
23 } catch (e) { 18 } catch (e) {
24 console.log(e); 19 console.log(e);
25 } 20 }
26 }; 21 };
22 +
23 +exports.randomItem = function (a) {
24 + return a[Math.floor(Math.random() * a.length)];
25 +};
......
1 const Slack = require("slack-node"); // 슬랙 모듈 사용 1 const Slack = require("slack-node"); // 슬랙 모듈 사용
2 2
3 +/*
3 const webhookUri = 4 const webhookUri =
4 - "https://hooks.slack.com/services/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"; // Webhook URL 5 + "https://hooks.slack.com/services/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"; // Webhook URL
6 +*/
5 7
6 -const slack = new Slack(); 8 +exports.send = async (message, webhookUri) => {
7 -slack.setWebhook(webhookUri); 9 + const slack = new Slack();
8 - 10 + slack.setWebhook(webhookUri);
9 -const send = async (message) => {
10 slack.webhook( 11 slack.webhook(
11 { 12 {
12 text: message, 13 text: message,
...@@ -16,5 +17,3 @@ const send = async (message) => { ...@@ -16,5 +17,3 @@ const send = async (message) => {
16 } 17 }
17 ); 18 );
18 }; 19 };
19 -
20 -send("hello");
......
1 var getBJ = require("./getBJ"); 1 var getBJ = require("./getBJ");
2 var fs = require("fs"); 2 var fs = require("fs");
3 3
4 -let dataset = [
5 - "1517",
6 - "2448",
7 - "1891",
8 - "1074",
9 - "2263",
10 - "1780",
11 - "11728",
12 - "10816",
13 - "10815",
14 - "2109",
15 - "1202",
16 - "1285",
17 - "2138",
18 - "1080",
19 - "11399",
20 - "1931",
21 - "11047",
22 - "15666",
23 - "15665",
24 - "15664",
25 - "15663",
26 - "15657",
27 - "15656",
28 - "15655",
29 - "15654",
30 - "15652",
31 - "15651",
32 - "15650",
33 - "15649",
34 - "6603",
35 - "10971",
36 - "10819",
37 - "10973",
38 - "10974",
39 - "10972",
40 - "7576",
41 - "1248",
42 - "2529",
43 - "15661",
44 - "14501",
45 - "1759",
46 - "14391",
47 - "14889",
48 - "1182",
49 - "11723",
50 - "1748",
51 - "6064",
52 - "1107",
53 - "3085",
54 - "2309",
55 - "1748",
56 - "14500",
57 - "1107",
58 - "1476",
59 - "3085",
60 - "2309",
61 - "1261",
62 - "13549",
63 - "14226",
64 - "13913",
65 - "1697",
66 - "1967",
67 - "1167",
68 - "11725",
69 - "2250",
70 - "1991",
71 - "7562",
72 - "2178",
73 - "4963",
74 - "2667",
75 - "1707",
76 - "11724",
77 - "1260",
78 - "13023",
79 - "11652",
80 - "1377",
81 - "11004",
82 - "10825",
83 - "2751",
84 - "9461",
85 - "1699",
86 - "9095",
87 - "2225",
88 - "2133",
89 - "11727",
90 - "11726",
91 - "1463",
92 - "2748",
93 - "2747",
94 - "11656",
95 - "10824",
96 - "2743",
97 - "10820",
98 - "10808",
99 - "11655",
100 - "11720",
101 - "1008",
102 - "10951",
103 - "2557",
104 - "1021",
105 - "1966",
106 - "2164",
107 - "10799",
108 - "17413",
109 - "10866",
110 - "1158",
111 - "10845",
112 - "1406",
113 - "1874",
114 - "9012",
115 - "9093",
116 - "10828",
117 - "11721",
118 - "11719",
119 - "11718",
120 - "10953",
121 - "2558",
122 - "10814",
123 - "1181",
124 - "11651",
125 - "11650",
126 - "1427",
127 - "2108",
128 - "10989",
129 - "2751",
130 - "2750",
131 - "1436",
132 - "1018",
133 - "7568",
134 - "2231",
135 - "2798",
136 - "1002",
137 - "3053",
138 - "4153",
139 - "3009",
140 - "1085",
141 - "9020",
142 - "4948",
143 - "1929",
144 - "2581",
145 - "1978",
146 - "2292",
147 - "6064",
148 - "2775",
149 - "10250",
150 - "2869",
151 - "1011",
152 - "1193",
153 - "2839",
154 - "1712",
155 - "1316",
156 - "2941",
157 - "5622",
158 - "2908",
159 - "1152",
160 - "1157",
161 - "2675",
162 - "10809",
163 - "11720",
164 - "11654",
165 - "11729",
166 - "2447",
167 - "3052",
168 - "10818",
169 - "10872",
170 - "10870",
171 - "1065",
172 - "4673",
173 - "15596",
174 - "4344",
175 - "2920",
176 - "8958",
177 - "1546",
178 - "2577",
179 - "2562",
180 - "1110",
181 - "10951",
182 - "10952",
183 - "10871",
184 - "2439",
185 - "2438",
186 - "11022",
187 - "11021",
188 - "2742",
189 - "2741",
190 - "15552",
191 - "8393",
192 - "10950",
193 - "2739",
194 - "10817",
195 - "2884",
196 - "2753",
197 - "9498",
198 - "1330",
199 - "2588",
200 - "10430",
201 - "10869",
202 - "1008",
203 - "10998",
204 - "7287",
205 - "10172",
206 - "10171",
207 - "10718",
208 - "1001",
209 - "1000",
210 - "2557",
211 -];
212 -
213 const test = async (userid) => { 4 const test = async (userid) => {
214 let lst = await getBJ.getBJ(userid); 5 let lst = await getBJ.getBJ(userid);
215 let return_lst = []; 6 let return_lst = [];
...@@ -217,7 +8,7 @@ const test = async (userid) => { ...@@ -217,7 +8,7 @@ const test = async (userid) => {
217 return_lst.push(lst[i].problem_number); 8 return_lst.push(lst[i].problem_number);
218 } 9 }
219 10
220 - var stringJson = JSON.stringify(return_lst) + "\n"; 11 + var stringJson = JSON.stringify(lst) + "\n";
221 fs.open("test.json", "a", "666", function (err, id) { 12 fs.open("test.json", "a", "666", function (err, id) {
222 if (err) { 13 if (err) {
223 console.log("file open err!!"); 14 console.log("file open err!!");
...@@ -232,4 +23,4 @@ const test = async (userid) => { ...@@ -232,4 +23,4 @@ const test = async (userid) => {
232 /* 23 /*
233 24
234 */ 25 */
235 -test("jwseo001"); 26 +test("thak00");
......
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.