송용우

Merge commit '0babf3c1' into feature/rest_api

Showing 38 changed files with 570 additions and 100 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",
......
...@@ -5,6 +5,7 @@ import LoginPage from './pages/LoginPage'; ...@@ -5,6 +5,7 @@ import LoginPage from './pages/LoginPage';
5 import RegisterPage from './pages/RegisterPage'; 5 import RegisterPage from './pages/RegisterPage';
6 import HomePage from './pages/HomePage'; 6 import HomePage from './pages/HomePage';
7 import SettingPage from './pages/SettingPage'; 7 import SettingPage from './pages/SettingPage';
8 +import ChallengePage from './pages/ChallengePage';
8 9
9 function App() { 10 function App() {
10 return ( 11 return (
...@@ -13,6 +14,7 @@ function App() { ...@@ -13,6 +14,7 @@ function App() {
13 <Route component={LoginPage} path="/login" /> 14 <Route component={LoginPage} path="/login" />
14 <Route component={RegisterPage} path="/register" /> 15 <Route component={RegisterPage} path="/register" />
15 <Route component={SettingPage} path="/setting" /> 16 <Route component={SettingPage} path="/setting" />
17 + <Route component={ChallengePage} path="/challenge" />
16 </> 18 </>
17 ); 19 );
18 } 20 }
......
1 +import React from 'react';
2 +import { makeStyles } from '@material-ui/core/styles';
3 +import Paper from '@material-ui/core/Paper';
4 +import Grid from '@material-ui/core/Grid';
5 +import palette from '../../lib/styles/palette';
6 +const ChallengeForm = () => {
7 + return <div></div>;
8 +};
9 +
10 +/*
11 +Todo
12 +챌린지 이름
13 +챌린지 기간 (Start - End)
14 +챌린지 세션 정보 (일 간격과 목표 문제)
15 +그룹 원 정보.
16 +*/
17 +export default ChallengeForm;
...@@ -8,6 +8,10 @@ const categories = [ ...@@ -8,6 +8,10 @@ const categories = [
8 text: '홈', 8 text: '홈',
9 }, 9 },
10 { 10 {
11 + name: 'challenge',
12 + text: '챌린지',
13 + },
14 + {
11 name: 'setting', 15 name: 'setting',
12 text: '설정', 16 text: '설정',
13 }, 17 },
......
...@@ -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">
27 - 등록
28 - </Button>
29 </form> 29 </form>
30 - <Button variant="outlined" onClick={onSyncBJIDSubmit}> 30 + <Button
31 + className={classes.button}
32 + variant="outlined"
33 + onClick={onBJIDSubmit}
34 + color="primary"
35 + >
36 + 등록
37 + </Button>
38 + <Button
39 + className={classes.button}
40 + variant="outlined"
41 + onClick={onSyncBJIDSubmit}
42 + color="secondary"
43 + >
31 동기화 44 동기화
32 </Button> 45 </Button>
33 </div> 46 </div>
......
...@@ -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 GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => { 18 const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => {
16 const classes = useStyles(); 19 const classes = useStyles();
17 return ( 20 return (
18 <div> 21 <div>
19 - <form onSubmit={onGoalNumSubmit}> 22 + <form>
20 <TextField 23 <TextField
21 name="goalNum" 24 name="goalNum"
22 type="number" 25 type="number"
...@@ -28,10 +31,15 @@ const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => { ...@@ -28,10 +31,15 @@ const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => {
28 shrink: true, 31 shrink: true,
29 }} 32 }}
30 /> 33 />
31 - <Button variant="outlined" type="submit">
32 - 등록
33 - </Button>
34 </form> 34 </form>
35 + <Button
36 + className={classes.button}
37 + onClick={onGoalNumSubmit}
38 + color="primary"
39 + variant="outlined"
40 + >
41 + 등록
42 + </Button>
35 </div> 43 </div>
36 ); 44 );
37 }; 45 };
......
...@@ -13,11 +13,12 @@ const useStyles = makeStyles((theme) => ({ ...@@ -13,11 +13,12 @@ const useStyles = makeStyles((theme) => ({
13 root: { 13 root: {
14 flexGrow: 1, 14 flexGrow: 1,
15 background: palette.gray[2], 15 background: palette.gray[2],
16 + padding: theme.spacing(8),
16 }, 17 },
17 paper: { 18 paper: {
19 + padding: theme.spacing(8),
18 margin: 'auto', 20 margin: 'auto',
19 textAlign: 'center', 21 textAlign: 'center',
20 - padding: 30,
21 }, 22 },
22 })); 23 }));
23 24
...@@ -45,14 +46,10 @@ const SettingForm = ({ ...@@ -45,14 +46,10 @@ const SettingForm = ({
45 </LoadingParentStyle> 46 </LoadingParentStyle>
46 ) : ( 47 ) : (
47 <div className={classes.root}> 48 <div className={classes.root}>
48 - <Grid container spacing={3}> 49 + <Grid container spacing={5}>
49 - <Grid item xs={12}> 50 + <Grid container item xs={6}>
50 - <Paper className={classes.paper}>
51 - <h3>{profile.username}</h3>
52 - </Paper>
53 - </Grid>
54 - <Grid container item xs={12}>
55 <Paper className={classes.paper} elevation={3}> 51 <Paper className={classes.paper} elevation={3}>
52 + <h1>백준 아이디</h1>
56 <BJIDForm 53 <BJIDForm
57 profile={profile} 54 profile={profile}
58 onChange={onChange} 55 onChange={onChange}
...@@ -62,8 +59,9 @@ const SettingForm = ({ ...@@ -62,8 +59,9 @@ const SettingForm = ({
62 </Paper> 59 </Paper>
63 </Grid> 60 </Grid>
64 61
65 - <Grid container item xs={12}> 62 + <Grid container item xs={6}>
66 <Paper className={classes.paper} elevation={3}> 63 <Paper className={classes.paper} elevation={3}>
64 + <h1>슬랙 Hook URL</h1>
67 <SlackForm 65 <SlackForm
68 profile={profile} 66 profile={profile}
69 onChange={onChange} 67 onChange={onChange}
...@@ -72,8 +70,9 @@ const SettingForm = ({ ...@@ -72,8 +70,9 @@ const SettingForm = ({
72 </Paper> 70 </Paper>
73 </Grid> 71 </Grid>
74 72
75 - <Grid container item xs={12}> 73 + <Grid container item xs={6}>
76 <Paper className={classes.paper} elevation={3}> 74 <Paper className={classes.paper} elevation={3}>
75 + <h1>일일 목표</h1>
77 <GoalNumForm 76 <GoalNumForm
78 profile={profile} 77 profile={profile}
79 onChange={onChange} 78 onChange={onChange}
......
...@@ -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">
28 - 등록
29 - </Button>
30 </form> 30 </form>
31 + <Button
32 + className={classes.button}
33 + onClick={onSlackURLSubmit}
34 + variant="outlined"
35 + type="submit"
36 + color="primary"
37 + >
38 + 등록
39 + </Button>
31 </div> 40 </div>
32 ); 41 );
33 }; 42 };
......
1 +import React from 'react';
2 +import { useSelector, useDispatch } from 'react-redux';
3 +import { withRouter } from 'react-router-dom';
4 +import ChallengeForm from '../../components/challenge/ChallengeForm';
5 +
6 +const ChallengeContainer = () => {
7 + return <div></div>;
8 +};
9 +
10 +export default ChallengeContainer;
...@@ -2,22 +2,35 @@ import React, { useEffect } from 'react'; ...@@ -2,22 +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 const HomeContainer = ({ history }) => { 6 const HomeContainer = ({ history }) => {
7 const dispatch = useDispatch(); 7 const dispatch = useDispatch();
8 const { user, profile } = useSelector(({ user, profile }) => ({ 8 const { user, profile } = useSelector(({ user, profile }) => ({
9 user: user.user, 9 user: user.user,
10 profile: profile, 10 profile: profile,
11 })); 11 }));
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]);
12 useEffect(() => { 25 useEffect(() => {
13 console.log(profile); 26 console.log(profile);
14 - }, [profile.solvedBJ]); 27 + }, [profile]);
15 useEffect(() => { 28 useEffect(() => {
16 if (user) { 29 if (user) {
17 let username = user.username; 30 let username = user.username;
18 dispatch(getPROFILE({ username })); 31 dispatch(getPROFILE({ username }));
19 } 32 }
20 }, [dispatch, user]); 33 }, [dispatch, user]);
21 - return <HomeForm />; 34 + return <HomeForm PSdata={profile.solvedBJ_date} goalNum={profile.goalNum} />;
22 }; 35 };
23 export default withRouter(HomeContainer); 36 export default withRouter(HomeContainer);
......
1 import React, { useEffect, useState } 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,
...@@ -14,6 +15,7 @@ import SettingForm from '../../components/setting/SettingForm'; ...@@ -14,6 +15,7 @@ import SettingForm from '../../components/setting/SettingForm';
14 15
15 const SettingContainer = ({ history }) => { 16 const SettingContainer = ({ history }) => {
16 const [isLoading, setLoading] = useState(false); 17 const [isLoading, setLoading] = useState(false);
18 +
17 const dispatch = useDispatch(); 19 const dispatch = useDispatch();
18 const { user, profile, loading } = useSelector( 20 const { user, profile, loading } = useSelector(
19 ({ user, profile, loading }) => ({ 21 ({ user, profile, loading }) => ({
...@@ -63,7 +65,7 @@ const SettingContainer = ({ history }) => { ...@@ -63,7 +65,7 @@ const SettingContainer = ({ history }) => {
63 useEffect(() => { 65 useEffect(() => {
64 if (!user) { 66 if (!user) {
65 alert('로그인이 필요합니다 '); 67 alert('로그인이 필요합니다 ');
66 - history.push('/'); 68 + history.push('/login');
67 } else { 69 } else {
68 let username = user.username; 70 let username = user.username;
69 dispatch(getPROFILE({ username })); 71 dispatch(getPROFILE({ username }));
...@@ -81,16 +83,18 @@ const SettingContainer = ({ history }) => { ...@@ -81,16 +83,18 @@ const SettingContainer = ({ history }) => {
81 }, [dispatch, loading]); 83 }, [dispatch, loading]);
82 84
83 return ( 85 return (
84 - <SettingForm 86 + <div>
85 - type="setting" 87 + <SettingForm
86 - onChange={onChange} 88 + type="setting"
87 - onBJIDSubmit={onBJIDSubmit} 89 + onChange={onChange}
88 - onSyncBJIDSubmit={onSyncBJIDSubmit} 90 + onBJIDSubmit={onBJIDSubmit}
89 - onSlackURLSubmit={onSlackURLSubmit} 91 + onSyncBJIDSubmit={onSyncBJIDSubmit}
90 - onGoalNumSubmit={onGoalNumSubmit} 92 + onSlackURLSubmit={onSlackURLSubmit}
91 - profile={profile} 93 + onGoalNumSubmit={onGoalNumSubmit}
92 - isLoading={isLoading} 94 + profile={profile}
93 - ></SettingForm> 95 + isLoading={isLoading}
96 + ></SettingForm>
97 + </div>
94 ); 98 );
95 }; 99 };
96 100
......
1 +import React from 'react';
2 +import HeaderContainer from '../containers/common/HeaderContainer';
3 +import ChallengeContainer from '../containers/challenge/ChallengeContainer';
4 +const ChallengePage = () => {
5 + return (
6 + <div>
7 + <HeaderContainer />
8 + <ChallengeContainer />
9 + </div>
10 + );
11 +};
12 +
13 +export default ChallengePage;
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 | 추천 문제 조회 | POST | api/profile/recommend | 바로가기 | None |
34 -| notify | 슬랙 메시지 전송 요청 (성취여부) | POST | api/notify/goal | 바로가기 | Jwt Token |
35 -| notify | 슬랙 메시지 전송 요청 (문제 추천) | POST | api/notify/recommend | 바로가기 | None |
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 }
......
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
1 let moment = require("moment"); 1 let moment = require("moment");
2 - 2 +const problem_set = require("../data/problem_set");
3 +const compareBJ = require("./compareBJ");
3 exports.analyzeBJ = function (solvedBJ) { 4 exports.analyzeBJ = function (solvedBJ) {
4 try { 5 try {
5 if (solvedBJ) { 6 if (solvedBJ) {
...@@ -7,6 +8,7 @@ exports.analyzeBJ = function (solvedBJ) { ...@@ -7,6 +8,7 @@ exports.analyzeBJ = function (solvedBJ) {
7 let presentDate_str = presentDate.format("YYYYMMDD"); 8 let presentDate_str = presentDate.format("YYYYMMDD");
8 let latestDate = moment(solvedBJ[0].solved_date, "YYYYMMDD"); 9 let latestDate = moment(solvedBJ[0].solved_date, "YYYYMMDD");
9 let difflatest = presentDate.diff(latestDate, "days"); 10 let difflatest = presentDate.diff(latestDate, "days");
11 + let latestSolve = solvedBJ[0];
10 12
11 let solvedBJbyDATE = {}; 13 let solvedBJbyDATE = {};
12 for (let i = 0; i < solvedBJ.length; i++) { 14 for (let i = 0; i < solvedBJ.length; i++) {
...@@ -23,12 +25,44 @@ exports.analyzeBJ = function (solvedBJ) { ...@@ -23,12 +25,44 @@ exports.analyzeBJ = function (solvedBJ) {
23 presentDate_str in solvedBJbyDATE 25 presentDate_str in solvedBJbyDATE
24 ? solvedBJbyDATE[presentDate_str].length 26 ? solvedBJbyDATE[presentDate_str].length
25 : 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 +
26 let returnOBJ = { 55 let returnOBJ = {
27 latestDate: latestDate.format("YYYYMMDD"), 56 latestDate: latestDate.format("YYYYMMDD"),
28 difflatest: difflatest, 57 difflatest: difflatest,
29 latestNum: latestNum, 58 latestNum: latestNum,
30 presentNum: presentNum, 59 presentNum: presentNum,
60 + weekNum: weekNUM,
61 + monthNum: monthNUM,
62 + totalNum: totalNUM,
31 solvedBJbyDATE: solvedBJbyDATE, 63 solvedBJbyDATE: solvedBJbyDATE,
64 + latestSolve: latestSolve,
65 + recommend_data: recommend_data,
32 }; 66 };
33 67
34 return returnOBJ; 68 return returnOBJ;
......
...@@ -14,7 +14,6 @@ exports.compareBJ = function (solvedBJ_new, problem_set) { ...@@ -14,7 +14,6 @@ exports.compareBJ = function (solvedBJ_new, problem_set) {
14 new_obj.push(problem_set[i]); 14 new_obj.push(problem_set[i]);
15 } 15 }
16 } 16 }
17 - console.log(new_obj);
18 return new_obj; 17 return new_obj;
19 } catch (e) { 18 } catch (e) {
20 console.log(e); 19 console.log(e);
......
...@@ -23,4 +23,4 @@ const test = async (userid) => { ...@@ -23,4 +23,4 @@ const test = async (userid) => {
23 /* 23 /*
24 24
25 */ 25 */
26 -test("jwseo001"); 26 +test("thak00");
......
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.