송용우

Merge commit '51d6adc9' into feature/rest_api

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 +}));
14 +
15 +const GoalNumForm = ({ onChange, profile, onGoalNumSubmit }) => {
16 + const classes = useStyles();
17 + return (
18 + <div>
19 + <form onSubmit={onGoalNumSubmit}>
20 + <TextField
21 + name="goalNum"
22 + type="number"
23 + onChange={onChange}
24 + value={profile.goalNum}
25 + placeholder="일일 목표"
26 + label="일일 목표"
27 + InputLabelProps={{
28 + shrink: true,
29 + }}
30 + />
31 + <Button variant="outlined" type="submit">
32 + 등록
33 + </Button>
34 + </form>
35 + </div>
36 + );
37 +};
38 +
39 +export default GoalNumForm;
...@@ -2,9 +2,12 @@ import React from 'react'; ...@@ -2,9 +2,12 @@ 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: {
...@@ -18,15 +21,29 @@ const useStyles = makeStyles((theme) => ({ ...@@ -18,15 +21,29 @@ const useStyles = makeStyles((theme) => ({
18 }, 21 },
19 })); 22 }));
20 23
24 +const LoadingParentStyle = styled.div`
25 + display: flex;
26 + flex-direction: column;
27 + justify-content: center;
28 + align-items: center;
29 + padding-top: 20px;
30 +`;
31 +
21 const SettingForm = ({ 32 const SettingForm = ({
22 onChange, 33 onChange,
23 onBJIDSubmit, 34 onBJIDSubmit,
24 onSlackURLSubmit, 35 onSlackURLSubmit,
25 profile, 36 profile,
26 onSyncBJIDSubmit, 37 onSyncBJIDSubmit,
38 + onGoalNumSubmit,
39 + isLoading,
27 }) => { 40 }) => {
28 const classes = useStyles(); 41 const classes = useStyles();
29 - return ( 42 + return isLoading ? (
43 + <LoadingParentStyle>
44 + <CircularProgress className={classes.loading} />
45 + </LoadingParentStyle>
46 + ) : (
30 <div className={classes.root}> 47 <div className={classes.root}>
31 <Grid container spacing={3}> 48 <Grid container spacing={3}>
32 <Grid item xs={12}> 49 <Grid item xs={12}>
...@@ -54,6 +71,16 @@ const SettingForm = ({ ...@@ -54,6 +71,16 @@ const SettingForm = ({
54 /> 71 />
55 </Paper> 72 </Paper>
56 </Grid> 73 </Grid>
74 +
75 + <Grid container item xs={12}>
76 + <Paper className={classes.paper} elevation={3}>
77 + <GoalNumForm
78 + profile={profile}
79 + onChange={onChange}
80 + onGoalNumSubmit={onGoalNumSubmit}
81 + />
82 + </Paper>
83 + </Grid>
57 </Grid> 84 </Grid>
58 </div> 85 </div>
59 ); 86 );
......
...@@ -9,7 +9,9 @@ const HomeContainer = ({ history }) => { ...@@ -9,7 +9,9 @@ const HomeContainer = ({ history }) => {
9 user: user.user, 9 user: user.user,
10 profile: profile, 10 profile: profile,
11 })); 11 }));
12 - useEffect(() => {}, [profile.solvedBJ]); 12 + useEffect(() => {
13 + console.log(profile);
14 + }, [profile.solvedBJ]);
13 useEffect(() => { 15 useEffect(() => {
14 if (user) { 16 if (user) {
15 let username = user.username; 17 let username = user.username;
......
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 import { withRouter } from 'react-router-dom'; 3 import { withRouter } from 'react-router-dom';
4 import { 4 import {
...@@ -8,15 +8,20 @@ import { ...@@ -8,15 +8,20 @@ import {
8 syncBJID, 8 syncBJID,
9 initializeProfile, 9 initializeProfile,
10 setSLACK, 10 setSLACK,
11 + setGOALNUM,
11 } from '../../modules/profile'; 12 } from '../../modules/profile';
12 import SettingForm from '../../components/setting/SettingForm'; 13 import SettingForm from '../../components/setting/SettingForm';
13 14
14 const SettingContainer = ({ history }) => { 15 const SettingContainer = ({ history }) => {
16 + const [isLoading, setLoading] = useState(false);
15 const dispatch = useDispatch(); 17 const dispatch = useDispatch();
16 - const { user, profile } = useSelector(({ user, profile }) => ({ 18 + const { user, profile, loading } = useSelector(
19 + ({ user, profile, loading }) => ({
17 user: user.user, 20 user: user.user,
18 profile: profile, 21 profile: profile,
19 - })); 22 + loading: loading,
23 + }),
24 + );
20 25
21 const onChange = (e) => { 26 const onChange = (e) => {
22 const { value, name } = e.target; 27 const { value, name } = e.target;
...@@ -33,6 +38,13 @@ const SettingContainer = ({ history }) => { ...@@ -33,6 +38,13 @@ const SettingContainer = ({ history }) => {
33 let username = profile.username; 38 let username = profile.username;
34 dispatch(syncBJID({ username })); 39 dispatch(syncBJID({ username }));
35 }; 40 };
41 +
42 + const onGoalNumSubmit = (e) => {
43 + e.preventDefault();
44 + let username = profile.username;
45 + let goalNum = profile.goalNum;
46 + dispatch(setGOALNUM({ username, goalNum }));
47 + };
36 const onSlackURLSubmit = (e) => { 48 const onSlackURLSubmit = (e) => {
37 e.preventDefault(); 49 e.preventDefault();
38 let username = profile.username; 50 let username = profile.username;
...@@ -60,6 +72,13 @@ const SettingContainer = ({ history }) => { ...@@ -60,6 +72,13 @@ const SettingContainer = ({ history }) => {
60 }; 72 };
61 } 73 }
62 }, [dispatch, user, history]); 74 }, [dispatch, user, history]);
75 + useEffect(() => {
76 + if (loading['profile/SYNC_BJID'] == true) {
77 + setLoading(true);
78 + } else {
79 + setLoading(false);
80 + }
81 + }, [dispatch, loading]);
63 82
64 return ( 83 return (
65 <SettingForm 84 <SettingForm
...@@ -68,7 +87,9 @@ const SettingContainer = ({ history }) => { ...@@ -68,7 +87,9 @@ const SettingContainer = ({ history }) => {
68 onBJIDSubmit={onBJIDSubmit} 87 onBJIDSubmit={onBJIDSubmit}
69 onSyncBJIDSubmit={onSyncBJIDSubmit} 88 onSyncBJIDSubmit={onSyncBJIDSubmit}
70 onSlackURLSubmit={onSlackURLSubmit} 89 onSlackURLSubmit={onSlackURLSubmit}
90 + onGoalNumSubmit={onGoalNumSubmit}
71 profile={profile} 91 profile={profile}
92 + isLoading={isLoading}
72 ></SettingForm> 93 ></SettingForm>
73 ); 94 );
74 }; 95 };
......
...@@ -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,
......
...@@ -1321,3 +1321,23 @@ ...@@ -1321,3 +1321,23 @@
1321 ::1 - - [24/Jun/2020:14:15:40 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0" 1321 ::1 - - [24/Jun/2020:14:15:40 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0"
1322 ::1 - - [24/Jun/2020:14:19:33 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0" 1322 ::1 - - [24/Jun/2020:14:19:33 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0"
1323 ::1 - - [24/Jun/2020:14:19:36 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0" 1323 ::1 - - [24/Jun/2020:14:19:36 +0000] "POST /api/profile/recommend HTTP/1.1" 404 9 "-" "PostmanRuntime/7.25.0"
1324 +::ffff:127.0.0.1 - - [24/Jun/2020:15:35:00 +0000] "POST /api/profile/setprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1325 +::ffff:127.0.0.1 - - [24/Jun/2020:15:35:04 +0000] "POST /api/profile/getprofile HTTP/1.1" 401 12 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1326 +::ffff:127.0.0.1 - - [24/Jun/2020:15:35:04 +0000] "GET /api/auth/check HTTP/1.1" 200 55 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1327 +::ffff:127.0.0.1 - - [24/Jun/2020:15:35:05 +0000] "POST /api/profile/getprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1328 +::ffff:127.0.0.1 - - [24/Jun/2020:15:37:14 +0000] "POST /api/profile/setprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1329 +::ffff:127.0.0.1 - - [24/Jun/2020:15:37:50 +0000] "POST /api/profile/setprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1330 +::ffff:127.0.0.1 - - [24/Jun/2020:15:37:54 +0000] "GET /api/auth/check HTTP/1.1" 200 55 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1331 +::ffff:127.0.0.1 - - [24/Jun/2020:15:37:54 +0000] "POST /api/profile/getprofile HTTP/1.1" 401 12 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1332 +::ffff:127.0.0.1 - - [24/Jun/2020:15:37:55 +0000] "POST /api/profile/getprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1333 +::ffff:127.0.0.1 - - [24/Jun/2020:15:39:50 +0000] "POST /api/profile/getprofile HTTP/1.1" 401 12 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1334 +::ffff:127.0.0.1 - - [24/Jun/2020:15:39:50 +0000] "GET /api/auth/check HTTP/1.1" 200 55 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1335 +::ffff:127.0.0.1 - - [24/Jun/2020:15:39:50 +0000] "POST /api/profile/getprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1336 +::ffff:127.0.0.1 - - [24/Jun/2020:15:39:54 +0000] "POST /api/profile/setprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1337 +::ffff:127.0.0.1 - - [24/Jun/2020:15:40:00 +0000] "GET /api/auth/check HTTP/1.1" 200 55 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1338 +::ffff:127.0.0.1 - - [24/Jun/2020:15:40:00 +0000] "POST /api/profile/getprofile HTTP/1.1" 401 12 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1339 +::ffff:127.0.0.1 - - [24/Jun/2020:15:40:00 +0000] "POST /api/profile/getprofile HTTP/1.1" 200 33281 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1340 +::1 - - [24/Jun/2020:15:40:32 +0000] "POST /api/profile/setprofile HTTP/1.1" 200 33293 "-" "PostmanRuntime/7.25.0"
1341 +::ffff:127.0.0.1 - - [24/Jun/2020:15:41:51 +0000] "GET /api/auth/check HTTP/1.1" 200 55 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1342 +::ffff:127.0.0.1 - - [24/Jun/2020:15:41:51 +0000] "POST /api/profile/getprofile HTTP/1.1" 401 12 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
1343 +::ffff:127.0.0.1 - - [24/Jun/2020:15:41:51 +0000] "POST /api/profile/getprofile HTTP/1.1" 200 33293 "http://localhost:3000/setting" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 Edg/83.0.478.54"
......
...@@ -50,7 +50,7 @@ exports.setProfile = async (ctx) => { ...@@ -50,7 +50,7 @@ exports.setProfile = async (ctx) => {
50 //freindList: Joi.array().items(Joi.string()), 50 //freindList: Joi.array().items(Joi.string()),
51 }) 51 })
52 .unknown(); 52 .unknown();
53 - 53 + console.log(ctx.request.body);
54 const result = Joi.validate(ctx.request.body, schema); 54 const result = Joi.validate(ctx.request.body, schema);
55 if (result.error) { 55 if (result.error) {
56 ctx.status = 400; 56 ctx.status = 400;
......
...@@ -9,6 +9,7 @@ const ProfileSchema = new Schema({ ...@@ -9,6 +9,7 @@ const ProfileSchema = new Schema({
9 solvedBJ_date: Object, 9 solvedBJ_date: Object,
10 friendList: [String], 10 friendList: [String],
11 slackWebHookURL: String, 11 slackWebHookURL: String,
12 + goalNum: Number,
12 }); 13 });
13 ProfileSchema.statics.findByUsername = function (username) { 14 ProfileSchema.statics.findByUsername = function (username) {
14 return this.findOne({ username }); 15 return this.findOne({ username });
......