박민정

[docs] Change the folder structure

Showing 36 changed files with 1177 additions and 468 deletions
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
......@@ -8,23 +6,23 @@ In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
......@@ -35,7 +33,7 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
......@@ -47,24 +45,24 @@ To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
......
This diff could not be displayed because it is too large.
......@@ -3,21 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"antd": "^4.16.1",
"axios": "^0.21.1",
"http-proxy-middleware": "^2.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"redux": "^4.1.0",
"antd": "^3.24.1",
"axios": "^0.19.2",
"formik": "^1.5.8",
"moment": "^2.24.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-icons": "^3.7.0",
"react-redux": "^7.1.0-rc.1",
"react-router-dom": "^5.0.1",
"react-scripts": "3.4.1",
"redux": "^4.0.0",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.3.0",
"web-vitals": "^1.1.2"
"yup": "^0.27.0",
"core-js": "^3.6.4",
"react-app-polyfill": "^1.0.6"
},
"scripts": {
"start": "react-scripts start",
......@@ -26,10 +27,7 @@
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"extends": "react-app"
},
"browserslist": {
"production": [
......@@ -42,5 +40,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"http-proxy-middleware": "^1.0.3"
}
}
......
......@@ -2,14 +2,9 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
......
......@@ -6,16 +6,6 @@
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
......
......@@ -9,12 +9,16 @@ import {
import LandingPage from './components/views/LandingPage/LandingPage'
import LoginPage from './components/views/LoginPage/LoginPage'
import RegisterPage from './components/views/RegisterPage/RegisterPage'
import UploadPage from './components/views/UploadPage/UploadPage'
import auth from './hoc/authentication'
import Navigation from './components/views/NavBar/NavBar'
function App() {
return (
<Router>
<Navigation />
<div>
<Switch>
{/* null, false에 대한 옵션 설명은 auth로 가서 확인*/}
......@@ -24,6 +28,7 @@ function App() {
<Route exact path="/register" component={auth(RegisterPage, false)}/>
<Route exact path="/product/upload" component={auth(UploadPage, true)}/>
{/*
<Route exact path="/" component={LandingPage} />
......
// type들만 관리하는 곳
export const LOGIN_USER = "login_user";
export const REGISTER_USER = "resgier_user";
export const AUTH_USER = "auth_user";
\ No newline at end of file
export const LOGIN_USER = 'login_user';
export const REGISTER_USER = 'register_user';
export const AUTH_USER = 'auth_user';
export const LOGOUT_USER = 'logout_user';
......
import axios from 'axios';
import {
LOGIN_USER,
REGISTER_USER,
AUTH_USER,
LOGOUT_USER,
} from './types';
import { USER_SERVER } from '../components/Config.js';
export function loginUser(logInfo){
const request = axios.post(`${USER_SERVER}/login`,logInfo)// logInfo를 post로 전달
.then(response => response.data); // 서버에서 받은 데이터를 request에 저장
return { // return을 통해 Reducer로 보냄
// Reducer에서 previousState, action을 이용해 nextState로 만들기 때문 :: (previousState, action) => nextState
// request를 reducer로 보내는 작업
// action은 type과 response을 넣어줘야 함
type: LOGIN_USER,
payload: request // payroad == response
}
}
export function registerUser(dataToSubmit){
const request = axios.post(`${USER_SERVER}/register`,dataToSubmit)
.then(response => response.data);
return {
type: REGISTER_USER,
payload: request
}
}
export function auth(){
const request = axios.get(`${USER_SERVER}/auth`)
.then(response => response.data);
return {
type: AUTH_USER,
payload: request
}
}
export function logoutUser(){
const request = axios.get(`${USER_SERVER}/logout`)
.then(response => response.data);
return {
type: LOGOUT_USER,
payload: request
}
}
// Redux에 있는 Store에 Reducer들이 여러가지 있을 수 있다.
// -> why? : Reducer 안에서 하는 일은
// state가 어떻게 변하는지를 보여준 다음, 변한 마지막 값을 return 해주는 것.
// 웹서비스를 제작하면서 user state, comment state ... 등등 다양한 기능에 대한 state들이 존재할 수 있고
// 각각 state마다 reducer가 있어서 user reducer, comment reducer ... 등등 다양한 reducer들이 존재할 수 있음.
// ------------------------------------
// 이렇게 나눠진 다양한 reducer을 combineReducers을 통해 rootReducer에서 하나로 합쳐주는 기능을 만들 것임.
import { combineReducers } from 'redux';
import user from './user_reducer'; // user(회원가입, 로그인, 인증, 로그아웃 기능이 있음) reducer
// import comment from './comment_reducer'; // comment기능이 있을 때 reducer
import user from './user_reducer';
const rootReducer = combineReducers( {
user
})
const rootReducer = combineReducers({
user,
});
// 다른 곳에서도 rootReducer을 쓸 수 있도록
export default rootReducer;
\ No newline at end of file
......
import {
LOGIN_USER,
REGISTER_USER,
AUTH_USER
AUTH_USER,
LOGOUT_USER,
} from '../_actions/types';
// reducer은 (previousState, action) => (nextState)로
export default function (prevState = {}, action) {
switch (action.type) {
case LOGIN_USER:
return {...prevState, loginSuccess:action.payload} // 위의 prevState를 그대로 가져오고,
// user_action.js에 있는 payload를 그대로 가져와서 return.
// loginSuccess는 server/index.js 에서 login에 성공하면 json type으로 loginSuccess: true를 전달하라고 했기 때문
break;
export default function(state={},action){
switch(action.type){
case REGISTER_USER:
return {...prevState, success:action.payload}
// success는 server/index.js 에서 register에 성공하면 json type으로 success: true를 전달하라고 했기 때문
break;
return {...state, register: action.payload }
case LOGIN_USER:
return { ...state, loginSucces: action.payload }
case AUTH_USER:
return {...prevState, user:action.payload}
break;
return {...state, userData: action.payload }
case LOGOUT_USER:
return {...state }
default:
return prevState;
return state;
}
}
\ No newline at end of file
......
import React, { Suspense } from 'react';
import { Route, Switch } from "react-router-dom";
import Auth from "../hoc/auth";
// pages for this product
import LandingPage from "./views/LandingPage/LandingPage.js";
import LoginPage from "./views/LoginPage/LoginPage.js";
import RegisterPage from "./views/RegisterPage/RegisterPage.js";
import NavBar from "./views/NavBar/NavBar";
import Footer from "./views/Footer/Footer"
//null Anyone Can go inside
//true only logged in user can go inside
//false logged in user can't go inside
function App() {
return (
<Suspense fallback={(<div>Loading...</div>)}>
<NavBar />
<div style={{ paddingTop: '69px', minHeight: 'calc(100vh - 80px)' }}>
<Switch>
<Route exact path="/" component={Auth(LandingPage, null)} />
<Route exact path="/login" component={Auth(LoginPage, false)} />
<Route exact path="/register" component={Auth(RegisterPage, false)} />
</Switch>
</div>
<Footer />
</Suspense>
);
}
export default App;
//SERVER ROUTES
export const USER_SERVER = '/api/users';
import React from 'react'
import {Icon} from 'antd';
function Footer() {
return (
<div>
Footer
<div style={{
height: '80px', display: 'flex',
flexDirection: 'column', alignItems: 'center',
justifyContent: 'center', fontSize:'1rem'
}}>
<p> 2018110650 박민정</p>
</div>
)
}
export default Footer
Footer
\ No newline at end of file
......
import React, {useEffect} from 'react'
import axios from 'axios'
import { withRouter } from 'react-router-dom';
function LandingPage(props) {
// 로그아웃 버튼 클릭 됐을 때
const onLogoutClickedEvent = () => {
axios.get('/api/users/logout')
.then(response => {
// 만약 success:true이면 로그인 페이지로 가기
if(response.data.success)
props.history.push("/login");
else
alert("Fail to logout.")
})
}
// 랜딩페이지에 들어오자마자
useEffect(() => {
axios.get('/api/hello') // get request를 서버로 보냄 (endpoint는 /api/hello)
.then(response => console.log(response.data)) // 서버로부터 응답 받은 내용을 콘솔에 출력
}, [])
import React from 'react'
import { FaCode } from "react-icons/fa";
function LandingPage() {
return (
<div style={{justifyContent:'center', alignItems: 'center', display:'flex', width:'100%'}}>
<h1>시작 페이지</h1>
<button onClick ={onLogoutClickedEvent}> Logout </button>
</div>
<>
<div className="app">
..
</div>
</>
)
}
export default withRouter(LandingPage)
\ No newline at end of file
export default LandingPage
......
import axios from 'axios';
//import { response } from 'express';
import React from 'react'
import {useState} from 'react'
import {useDispatch} from 'react-redux';
import {loginUser} from '../../../_actions/user_action'
import { withRouter } from 'react-router-dom';
import React, { useState } from "react";
import { withRouter } from "react-router-dom";
import { loginUser } from "../../../_actions/user_actions";
import { Formik } from 'formik';
import * as Yup from 'yup';
import { Form, Icon, Input, Button, Checkbox, Typography } from 'antd';
import { useDispatch } from "react-redux";
const { Title } = Typography;
function LoginPage(props) {
const dispatch = useDispatch();
//const rememberMeChecked = localStorage.getItem("rememberMe") ? true : false;
const [Error, setError] = useState('')
//const [rememberMe, setRememberMe] = useState(rememberMeChecked)
// 이 로그인페이지 안에서 input에 타이핑을 함으로써 데이터를 변화시켜주므로 state 사용.
// 1-1. state을 사용하기 위해 state 만들어줌.
const [Email, setEmail] = useState(""); // 1-2. email을 위한 state
const [Password, setPassword] = useState(""); // 1-2. password를 위한 state
//1-3. 아래 input value에 넣어줌
// const handleRememberMe = () => {
// setRememberMe(!rememberMe)
// };
// 2-1. 타이핑할 때 타이핑 하는 거 보이게 하도록 핸들러를 만들어줌
const emailEvent = (event) => {
setEmail(event.currentTarget.value)
}
const passwordEvent = (event) => {
setPassword(event.currentTarget.value)
}
//const initialEmail = localStorage.getItem("rememberMe") ? localStorage.getItem("rememberMe") : '';
const dispatch = useDispatch();
const submitEvent = (event) => {
event.preventDefault(); // 이걸 하지 않으면 버튼을 누를 때마다 refresh돼서 데이터 처리를 할 수 없음
//console.log('Email', Email); // 잘 나오는지 확인
//console.log('Password', Password); // 잘 나오는지 확인
return (
<Formik
initialValues={{
email: '',
password: '',
}}
// validationSchema={Yup.object().shape({
// email: Yup.string()
// .email('Email is invalid')
// .required('Email is required'),
// password: Yup.string()
// .min(6, 'Password must be at least 6 characters')
// .required('Password is required'),
// })}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
let dataToSubmit = {
email: values.email,
password: values.password
};
let logInfo = { // 보내주기 위해 저장
email: Email,
password: Password
}
dispatch(loginUser(logInfo)) // _actions폴더 user_action.js에 있음
dispatch(loginUser(dataToSubmit))
.then(response => {
if(response.payload.loginSuccess)
props.history.push('/');
else
alert('Error');
if (response.payload.loginSuccess) {
window.localStorage.setItem('userId', response.payload.userId);
// if (rememberMe === true) {
// window.localStorage.setItem('rememberMe', values.id);
// } else {
// localStorage.removeItem('rememberMe');
// }
props.history.push("/");
} else {
setError('이메일 또는 비밀번호가 올바르지 않습니다.')
}
})
.catch(err => {
setError('이메일 또는 비밀번호가 올바르지 않습니다.')
setTimeout(() => {
setError("")
}, 3000);
});
setSubmitting(false);
}, 500);
}}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<div className="app">
<Title level={2}>로그인</Title>
<form onSubmit={handleSubmit} style={{ width: '350px' }}>
<Form.Item required>
<Input
id="email"
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Enter your email"
type="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email ? 'text-input error' : 'text-input'
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
</Form.Item>
})
}
return (
<div style={{
justifyContent:'center', alignItems: 'center', display:'flex', width:'100%', height:'50vh'
}}>
<form onSubmit={submitEvent}>
<label>Email</label>
<input type="email" value={Email} onChange={emailEvent} />
{/* input type="email"이라서 '이메일 주소에 '@'를 포함해주세요'라는 경고문 뜸. */}
<label>Password</label>
<input type="password" value={Password} onChange={passwordEvent} />
<br/>
<button>
Login
</button>
<Form.Item required>
<Input
id="password"
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Enter your password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.password && touched.password ? 'text-input error' : 'text-input'
}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
</Form.Item>
{Error && (
<label ><p style={{ color: '#508DFF', fontSize: '0.8rem' }}>{Error}</p></label>
)}
<Form.Item>
{/* <Checkbox id="rememberMe" onChange={handleRememberMe} checked={rememberMe} >Remember me</Checkbox>
<a className="login-form-forgot" href="/reset_user" style={{ float: 'right' }}>
forgot password
</a> */}
<div>
<Button type="primary" htmlType="submit" className="login-form-button" style={{ minWidth: '100%' }} disabled={isSubmitting} onSubmit={handleSubmit}>
로그인
</Button>
</div>
<a href="/register" style={{ minWidth: '100%' }}>회원가입</a>
</Form.Item>
</form>
</div>
)
}
</div>
);
}}
</Formik>
);
};
export default withRouter(LoginPage);
export default withRouter(LoginPage)
......
import React from 'react'
import React, { useState } from 'react';
import LeftMenu from './Sections/LeftMenu';
import RightMenu from './Sections/RightMenu';
import { Drawer, Button, Icon } from 'antd';
import './Sections/Navbar.css';
function NavBar() {
return (
<div>
NavBar
const [visible, setVisible] = useState(false)
const showDrawer = () => {
setVisible(true)
};
const onClose = () => {
setVisible(false)
};
return (
<nav className="menu" style={{ position: 'fixed', zIndex: 5, width: '100%' }}>
<div className="menu__logo">
<a href="/">Logo</a>
</div>
<div className="menu__container">
<div className="menu_left">
<LeftMenu mode="horizontal" />
</div>
<div className="menu_rigth">
<RightMenu mode="horizontal" />
</div>
)
<Button
className="menu__mobile-button"
type="primary"
onClick={showDrawer}
>
<Icon type="align-right" />
</Button>
<Drawer
title="Basic Drawer"
placement="right"
className="menu_drawer"
closable={false}
onClose={onClose}
visible={visible}
>
<LeftMenu mode="inline" />
<RightMenu mode="inline" />
</Drawer>
</div>
</nav>
)
}
export default NavBar
export default NavBar
\ No newline at end of file
......
import React from 'react';
import { Menu } from 'antd';
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;
function LeftMenu(props) {
return (
<Menu mode={props.mode}>
<Menu.Item key="mail">
<a href="/">Home</a>
</Menu.Item>
<SubMenu title={<span>Blogs</span>}>
<MenuItemGroup title="Item 1">
<Menu.Item key="setting:1">Option 1</Menu.Item>
<Menu.Item key="setting:2">Option 2</Menu.Item>
</MenuItemGroup>
<MenuItemGroup title="Item 2">
<Menu.Item key="setting:3">Option 3</Menu.Item>
<Menu.Item key="setting:4">Option 4</Menu.Item>
</MenuItemGroup>
</SubMenu>
</Menu>
)
}
export default LeftMenu
\ No newline at end of file
@import "~antd/dist/antd.css";
.menu {
padding: 0 20px;
border-bottom: solid 1px #e8e8e8;
overflow: auto;
box-shadow: 0 0 30px #f3f1f1;
background-color: white;
}
.menu__logo {
width: 150px;
float: left;
}
.menu__logo a {
display: inline-block;
font-size: 20px;
padding: 19px 20px;
}
.menu__container .ant-menu-item {
padding: 0px 5px;
}
.menu__container .ant-menu-submenu-title {
padding: 10px 20px;
}
.menu__container .ant-menu-item a,
.menu__container .ant-menu-submenu-title a {
padding: 10px 15px;
}
.menu__container .ant-menu-horizontal {
border-bottom: none;
}
.menu__container .menu_left {
float: left;
}
.menu__container .menu_rigth {
float: right;
}
.menu__mobile-button {
float: right;
height: 32px;
padding: 6px;
margin-top: 8px;
display: none !important; /* use of important to overwrite ant-btn */
background: #3e91f7;
}
.menu_drawer .ant-drawer-body {
padding: 0 !important;
}
/* align header of Drawer with header of page */
.menu_drawer .ant-drawer-header {
padding: 14px 24px !important;
}
@media (max-width: 767px) {
.menu__mobile-button {
display: inline-block !important;
}
.menu_left,
.menu_rigth {
display: none;
}
.menu__logo a {
margin-left: -20px;
}
.menu__container .ant-menu-item,
.menu__container .ant-menu-submenu-title {
padding: 1px 20px;
}
.menu__logo a {
padding: 10px 20px;
}
}
import React from 'react';
import { Menu } from 'antd';
import axios from 'axios';
import { USER_SERVER } from '../../../Config';
import { withRouter } from 'react-router-dom';
import { useSelector } from "react-redux";
function RightMenu(props) {
const user = useSelector(state => state.user)
const logoutHandler = () => {
axios.get(`${USER_SERVER}/logout`).then(response => {
if (response.status === 200) {
props.history.push("/login");
} else {
alert('Log Out Failed')
}
});
};
//console.log(user.userData);
//console.log(!user.userData.isAuth);
if (user.userData && !user.userData.isAuth) {
return (
<Menu mode={props.mode}>
<Menu.Item key="mail">
<a href="/login">로그인</a>
</Menu.Item>
<Menu.Item key="app">
<a href="/register">회원가입</a>
</Menu.Item>
</Menu>
)
} else {
return (
<Menu mode={props.mode}>
<Menu.Item key="logout">
<a onClick={logoutHandler}>로그아웃</a>
</Menu.Item>
</Menu>
)
}
}
export default withRouter(RightMenu);
import React from 'react';
import { Menu } from 'antd';
const SubMenu = Menu.SubMenu;
const MenuItemGroup = Menu.ItemGroup;
function LeftMenu(props) {
return (
<Menu mode={props.mode}>
<Menu.Item key="mail">
<a href="/">Home</a>
</Menu.Item>
<SubMenu title={<span>Blogs</span>}>
<MenuItemGroup title="Item 1">
<Menu.Item key="setting:1">Option 1</Menu.Item>
<Menu.Item key="setting:2">Option 2</Menu.Item>
</MenuItemGroup>
<MenuItemGroup title="Item 2">
<Menu.Item key="setting:3">Option 3</Menu.Item>
<Menu.Item key="setting:4">Option 4</Menu.Item>
</MenuItemGroup>
</SubMenu>
</Menu>
)
}
export default LeftMenu
\ No newline at end of file
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';
import { Menu } from 'antd';
import axios from 'axios';
//import { USER_SERVER } from '../../../Config';
import { withRouter } from 'react-router-dom';
import { useSelector } from "react-redux";
function RightMenu(props) {
const user = useSelector(state => state.user)
const logoutHandler = () => {
axios.get('/api/users/logout').then(response => {
if (response.status === 200) {
props.history.push("/login");
} else {
alert('Log Out Failed')
}
});
};
//console.log(user.userData)
//console.log(!user.userData.isAuth)
if (user.userData && !user.userData.isAuth) {
return (
<Menu mode={props.mode}>
<Menu.Item key="mail">
<a href="/login">Signin</a>
</Menu.Item>
<Menu.Item key="app">
<a href="/register">Signup</a>
</Menu.Item>
</Menu>
)
} else {
return (
<Menu mode={props.mode}>
<Menu.Item key="logout">
<a onClick={logoutHandler}>Logout</a>
</Menu.Item>
</Menu>
)
}
}
export default withRouter(RightMenu);
\ No newline at end of file
import React from 'react'
import {useState} from 'react'
import {useDispatch} from 'react-redux';
import {RegisterUser} from '../../../_actions/user_action'
import { withRouter } from 'react-router-dom';
import React from "react";
import moment from "moment";
import { Formik } from 'formik';
import * as Yup from 'yup';
import { registerUser } from "../../../_actions/user_actions";
import { useDispatch } from "react-redux";
function RegisterPage(props) {
// 이 로그인페이지 안에서 input에 타이핑을 함으로써 데이터를 변화시켜주므로 state 사용.
// 1-1. state을 사용하기 위해 state 만들어줌.
const [Name, setName] = useState("");
const [Email, setEmail] = useState(""); // 1-2. email을 위한 state
const [Password, setPassword] = useState(""); // 1-2. password를 위한 state
const [Password2, setPassword2] = useState("");
import {
Form,
Input,
Button,
} from 'antd';
//1-3. 아래 input value에 넣어줌
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
// 2-1. 타이핑할 때 타이핑 하는 거 보이게 하도록 핸들러를 만들어줌
const emailEvent = (event) => {
setEmail(event.currentTarget.value)
}
const passwordEvent = (event) => {
setPassword(event.currentTarget.value)
}
const password2Event = (event) => {
setPassword2(event.currentTarget.value)
}
const NameEvent = (event) => {
setName(event.currentTarget.value)
}
function RegisterPage(props) {
const dispatch = useDispatch();
return (
const dispatch = useDispatch();
const submitEvent = (event) => {
event.preventDefault(); // 이걸 하지 않으면 버튼을 누를 때마다 refresh돼서 데이터 처리를 할 수 없음
//console.log('Email', Email); // 잘 나오는지 확인
//console.log('Password', Password); // 잘 나오는지 확인
<Formik
initialValues={{
email: '',
name: '',
password: '',
confirmPassword: ''
}}
validationSchema={Yup.object().shape({
name: Yup.string()
.required('이름이 올바르게 입력되지 않았습니다.'),
email: Yup.string()
.required('이메일이 올바르게 입력되지 않았습니다.'),
password: Yup.string()
.min(3, '비밀번호는 3자리 이상이어야 합니다.')
.required('비밀번호를 입력해주세요.'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], '비밀번호를 다시 확인해주세요.')
.required('비밀번호 확인이 올바르게 입력되지 않았습니다.')
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
// 비밀번호 두개가 같아야 회원가입이 되도록
if(Password !== Password2)
return alert('비밀번호가 일치하지 않습니다.')
let dataToSubmit = {
email: values.email,
password: values.password,
name: values.name,
//image: `http://gravatar.com/avatar/${moment().unix()}?d=identicon`
};
dispatch(registerUser(dataToSubmit)).then(response => {
if (response.payload.success) {
props.history.push("/login");
} else {
alert(response.payload.err.errmsg)
}
})
let regiInfo = { // 보내주기 위해 저장
name : Name,
email: Email,
password: Password
}
dispatch(RegisterUser(regiInfo)) // _actions폴더 user_action.js에 있음
.then(response => {
if(response.payload.success)
props.history.push('/login');
else
alert('Fail to sign up');
})
}
setSubmitting(false);
}, 500);
}}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<div className="app">
<h2>회원가입</h2>
<Form style={{ minWidth: '375px' }} {...formItemLayout} onSubmit={handleSubmit} >
return (
<div style={{
justifyContent:'center', alignItems: 'center', display:'flex', width:'100%', height:'50vh'
}}>
<form onSubmit={submitEvent} style={{display: 'flex', flexDirection: 'column'}}>
<Form.Item required label="Name">
<Input
id="name"
placeholder="이름을 입력하세요."
type="text"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.name && touched.name ? 'text-input error' : 'text-input'
}
/>
{errors.name && touched.name && (
<div className="input-feedback">{errors.name}</div>
)}
</Form.Item>
<label>Name</label>
<input type="text" value={Name} onChange={NameEvent} />
<Form.Item required label="Email" hasFeedback>
<Input
id="email"
placeholder="이메일을 입력하세요."
type="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email ? 'text-input error' : 'text-input'
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
</Form.Item>
<label>Email</label>
<input type="email" value={Email} onChange={emailEvent} />
{/* input type="email"이라서 '이메일 주소에 '@'를 포함해주세요'라는 경고문 뜸. */}
<Form.Item required label="Password" hasFeedback>
<Input
id="password"
placeholder="비밀번호를 입력하세요."
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.password && touched.password ? 'text-input error' : 'text-input'
}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
</Form.Item>
<Form.Item required label="Confirm" hasFeedback>
<Input
id="confirmPassword"
placeholder="비밀번호를 다시 한 번 입력해주세요."
type="password"
value={values.confirmPassword}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.confirmPassword && touched.confirmPassword ? 'text-input error' : 'text-input'
}
/>
{errors.confirmPassword && touched.confirmPassword && (
<div className="input-feedback">{errors.confirmPassword}</div>
)}
</Form.Item>
<label>Password</label>
<input type="password" value={Password} onChange={passwordEvent} />
<Form.Item {...tailFormItemLayout}>
<Button onClick={handleSubmit} type="primary" disabled={isSubmitting}>
Submit
</Button>
</Form.Item>
</Form>
</div>
);
}}
</Formik>
);
};
<label>Confirm Password</label>
<input type="password" value={Password2} onChange={password2Event} />
<br/>
<button>
Sign In
</button>
</form>
</div>
)
}
export default withRouter(RegisterPage)
export default RegisterPage
......
import React from 'react'
function UploadPage() {
return (
<div>
uploadpage
</div>
)
}
export default UploadPage
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect } from 'react';
import { auth } from '../_actions/user_actions';
import { useSelector, useDispatch } from "react-redux";
export default function (SpecificComponent, option, adminRoute = null) {
function AuthenticationCheck(props) {
let user = useSelector(state => state.user);
const dispatch = useDispatch();
useEffect(() => {
//To know my current status, send Auth request
dispatch(auth()).then(response => {
//Not Loggined in Status
if (!response.payload.isAuth) {
if (option) {
props.history.push('/login')
}
//Loggined in Status
} else {
//supposed to be Admin page, but not admin person wants to go inside
if (adminRoute && !response.payload.isAdmin) {
props.history.push('/')
}
//Logged in Status, but Try to go into log in page
else {
if (option === false) {
props.history.push('/')
}
}
}
})
}, [])
return (
<SpecificComponent {...props} user={user} />
)
}
return AuthenticationCheck
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px;
line-height: 1.5;
color: #24292e;
background-color: #fff;
}
.app {
flex-direction: column;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
input.error {
border-color: red;
}
.input-feedback {
color: red;
height: 5px;
margin-top: -12px;
}
......
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/ie11';
import 'core-js';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux' // app에 redux를 연결시켜주기 위해 redux에서 제공하는 provider 사용
import { createStore } from 'redux'; // redux에서 createStore 가져옴.
import { applyMiddleware } from 'redux'; // object만 받는 store가 promise나 functions도 받기 위해 middleware을 사용함
import App from './components/App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from "react-router-dom";
import Reducer from './_reducers';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import Reducer from './_reducers';
import 'antd/dist/antd.css';
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore)
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
ReactDOM.render(
// App에 Redux를 연결
<Provider
store={createStoreWithMiddleware(Reducer,
<Provider
store={createStoreWithMiddleware(
Reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)}
>
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log resu lts (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
......
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
\ No newline at end of file
......
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
{
"name": "boiler-plate",
"name": "react-boiler-plate",
"version": "1.0.0",
"description": "",
"description": "react boiler plate",
"main": "index.js",
"engine": {
"node": "10.16.0",
"npm": "6.9.0"
},
"scripts": {
"start": "node server/index.js",
"backend": "nodemon server/index.js",
"test": "echo \"Error: no test specified\" && exit 1",
"frontend": "npm run start --prefix client",
"dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""
},
"author": "mindyeoi",
"author": "John ahn",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"concurrently": "^6.2.0",
"cookie-parser": "^1.4.5",
"bcrypt": "^3.0.6",
"body-parser": "^1.18.3",
"cookie-parser": "^1.4.3",
"cors": "^2.8.5",
"debug": "^4.1.1",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"mongoose": "^5.12.12"
"moment": "^2.24.0",
"mongoose": "^5.4.20",
"react-redux": "^5.0.7",
"saslprep": "^1.0.3",
"supports-color": "^7.1.0"
},
"devDependencies": {
"nodemon": "^2.0.7"
"concurrently": "^4.1.0",
"nodemon": "^1.19.1"
}
}
......
const express = require('express')
const express = require("express")
const app = express()
const port = 5000
const port = process.env.PORT || 5000
// User.js에서 만든 model을 가져옴
const { User } = require('./models/User')
//const { User } = require('./')
const path = require("path");
const cors = require('cors')
// body-parser 가져옴
const bodyParser = require('body-parser')
// bodyParser option
app.use(bodyParser.urlencoded({extended: true})) //application/x-www-form-urlencoded로 된 데이터를 분석해서 가져옴
app.use(bodyParser.json()) // application/json 타입으로 된 데이터를 분석해서 가져옴
const cookieParser = require("cookie-parser");
app.use(cookieParser());
const config = require("./config/key");
const config = require('./config/key')
const cookieParser = require('cookie-parser')
app.use(cookieParser())
const mongoose = require("mongoose");
const mongoose = require('mongoose')
const { auth } = require('./middleware/auth')
//이 정보는 비밀임..! 몽고DB아이디랑 비밀번호를 감춰야해..!
mongoose.connect(config.mongoURI, {
useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false
}).then(()=>console.log('MongoDB Connected...'))
.catch(err => console.log(err))
app.get('/', (req, res) => {
res.send('민정이짱짱맨최고최고!')
})
// 회원가입 구현
// route의 endpoint는 register
app.post('/api/users/register', (req, res) => {
// 회원가입할 때 필요한 정보들을 client에서 가져오면
// 그것들을 데이터베이스에 넣어준다.
const user = new User(req.body) // req.body에 User의 정보를 저장
// 비밀번호 암호화
const connect = mongoose.connect(config.mongoURI,
{
useNewUrlParser: true, useUnifiedTopology: true,
useCreateIndex: true, useFindAndModify: false
})
.then(() => console.log('MongoDB ---> Connected'))
.catch(err => console.log(err));
// mongoDB에서 오는 메서드. 정보들이 user model에 저장
user.save((err, userInfo) => {
// 만약 에러가 나면, json형식으로 success:false를 보내주고, 에러메시지를 보내줌
if(err) return res.json({success: false, err})
// 성공하면 status(200) (status(200)은 성공했다는 뜻)
return res.status(200).json({
success: true
})
})
app.use(cors())
})
// 로그인 구현 -> 로그인 하면 토큰 생성
app.post('/api/users/login', (req, res) => {
// 1. 요청된 이메일이 데이터베이스에 있는지 찾기
User.findOne({ email: req.body.email }, (err, user) => {
if(!user)
{
return res.json({
loginSuccess: false,
message: "There is no user with that email."
})
}
// 2. email과 비밀번호가 맞는지 확인 (User.js에 comparePassword 함수 정의되어 있음)
user.comparePassword(req.body.password, (err, isMatch) => {
if(!isMatch)
return res.json({loginSuccess: false, message: "Password is not match."})
// 3. 비밀번호까지 맞다면 유저를 위한 토큰 생성 (User.js에 generateToken 함수 정의)
user.generateToken((err, user) => { // err가 없으면 user에 정보 받아옴
if(err)
return res.status(400).send(err);
// 4. 생성한 토큰을 저장함 -> 쿠키나 로컬 스토리지 등에 저장할 수 있는데 여기선 쿠키에 저장
res.cookie("loginCookie", user.token)
.status(200) //성공했다는 표시
.json({loginSuccess: true, userId: user._id})
})
})
})
})
app.use('/api/users', require('./routes/users'));
// 인증 구현 (이 사람이 일반유저인지 관리자인지)
app.get('/api/users/auth', auth ,(req,res) => {
// 여기까지 미들웨어(auth) 통과했으면 authentication == true 라는 뜻
res.status(200).json({
_id: req.user._id,
isAdmin: req.user.role === 0 ? false : true, // role이 0이면 일반 유저, role이 0이 아니면 관리자.
isAuth: true,
email: req.user.email,
lastname: req.user.lastname,
role: req.user.role,
image: req.user.image
})
})
// 로그아웃 구현 (로그인 때 만든 토큰을 지워버림)
app.get('/api/users/logout', auth, (req, res) => {
User.findOneAndUpdate({_id: req.user._id}, // id로 User를 찾아서 업데이터 시킴
{ token: "" }, (err, user) => {
if(err) return res.json({success: false, err});
return res.status(200).send({
success: true
})
})
})
// 이미지 가져오려고
app.use('/uploads', express.static('uploads'));
// test
app.get('/api/hello', (req, res) => {
res.send("hello world");
})
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
\ No newline at end of file
console.log(`Server ---> http://localhost:${port}`)
});
\ No newline at end of file
......
const { User } = require("../models/User");
const { User } = require('../models/User');
let auth = (req, res, next) => {
// 인증 처리
// 1. client 쿠키에서 토큰을 가져옴.
let token = req.cookies.loginCookie;
// 인증 처리
// 1. client 쿠키에서 토큰을 가져옴.
let token = req.cookies.w_auth;
// 2. 토큰을 복호화한 후 유저를 찾는다. (User.js에 findByToken(); 있음)
User.findByToken(token, (err, user)=>{
// 에러가 있으면
if(err) throw err;
// 유저가 없으면
if(!user) return res.json({ isAuth:false, error: true})
// 에러도 없고 유저도 있으면
req.token = token; // token과 user를 request에 넣어줌으로써 index.js에서 request 사용할 수 있음
req.user = user;
next();
});
// 2. 토큰을 복호화한 후 유저를 찾는다. (User.js에 findByToken(); 있음)
User.findByToken(token, (err, user)=>{
// 에러가 있으면
if(err) throw err;
// 유저가 없으면
if(!user) return res.json({ isAuth:false, error: true})
// 에러도 없고 유저도 있으면
req.token = token; // token과 user를 request에 넣어줌으로써 index.js에서 request 사용할 수 있음
req.user = user;
next();
});
// 3. 유저가 있으면 인증OK, 유저가 없으면 인증No!
// 3. 유저가 있으면 인증OK, 유저가 없으면 인증No!
}
// 이 auth를 다른 파일에서도 쓸 수 있도록
......
......@@ -5,35 +5,36 @@ const bcrypt = require('bcrypt')
// bcrypt 사용하기 위해 salt를 생성하고 그걸 이용해 암호화 시킴
const saltRounds = 10 // salt를 몇글자 할 건지
//
const jwt = require('jsonwebtoken')
const jwt = require('jsonwebtoken');
const moment = require("moment");
const userSchema = mongoose.Schema({
name:{
type: String,
maxlength: 50
name: {
type:String,
maxlength:50
},
email:{
type: String,
trim: true, // 'minjeong park'처럼 space가 있는 문자에 space가 없어지도록 함
unique: 1 // 똑같은 이메일은 쓸 수 없도록
email: {
type:String,
trim:true,
unique: 1
},
password: {
type: String,
minlength: 5
minglength: 3
},
lastname: {
type: String,
type:String,
maxlength: 50
},
role: { // Number==1 이면 관리자, number==0 이면 일반 유저
type: Number,
default: 0 // default는 0
role : {
type:Number, // Number==1 이면 관리자, number==0 이면 일반 유저
default: 0 // default는 0
},
image: String,
token : {
type : String
type: String,
},
tokenExp: { //토큰의 유효기간
tokenExp :{
type: Number
}
})
......@@ -41,11 +42,11 @@ const userSchema = mongoose.Schema({
// index.js의 app.post('/register', (req, res)에 있는
// user model에 user 정보를 저장하기 전에 무엇을 한다는 것
// function( next )를 해서 얘네가 끝난 다음에 다음걸 실행해라~
userSchema.pre('save', function( next ){
var user = this
if(user.isModified('password')) // password를 변경할 때만 적용되도록..
{
userSchema.pre('save', function( next ) {
var user = this;
if(user.isModified('password')){ // password를 변경할 때만 적용되도록..
// 비밀번호 암호화 (https://www.npmjs.com/package/bcrypt 에서 가져옴)
bcrypt.genSalt(saltRounds, (err, salt) => // salt를 만드는 함수
{
......@@ -53,18 +54,15 @@ userSchema.pre('save', function( next ){
bcrypt.hash(user.password, salt, (err, hash) => { // bcrypt.hash(암호화되지 않은 pw, salt, function(err, 암호화된 비밀번호))
if(err) return next(err) // 에러 나면 return err
user.password = hash // 성공하면 user.password를 hash로 교체
next()
});
});
}
else
{
next()
})
})
} else {
next()
}
});
})
userSchema.methods.comparePassword = function(plainPassword, cb){
userSchema.methods.comparePassword = function(plainPassword,cb){
// 1. plainPassword가 1234567 암호화된 비밀번호 가 같은지 체크해야함
// 그러면 plainPassword도 암호화해서 비교해야함. (복호화 할 수 없기 때문에)
......@@ -75,39 +73,35 @@ userSchema.methods.comparePassword = function(plainPassword, cb){
})
}
userSchema.methods.generateToken = function(cb)
{
userSchema.methods.generateToken = function(cb) {
var user = this;
// jsonwebtoken을 이용해서 token 생성
var token = jwt.sign(user._id.toHexString(), 'secretToken') //database에 있는 id라서 _id
var oneHour = moment().add(1, 'hour').valueOf();
user.token = token
user.save(function(err, user){
if(err)
return cb(err) // 에러가 있다면 callback으로 에러 전달
user.tokenExp = oneHour;
user.token = token;
user.save(function (err, user){
if(err) return cb(err)// 에러가 있다면 callback으로 에러 전달
cb(null, user) // 에러가 없다면 err는 없고 user정보만 전달
})
}
userSchema.statics.findByToken = function(token, cb)
{
userSchema.statics.findByToken = function (token, cb) {
var user = this;
// 1. 토큰을 decoding
jwt.verify(token, 'secretToken', function(err, decoded) {
// 2. 유저 아이디를 이용해서 유저를 찾은 다음에 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인.
user.findOne({"_id": decoded, "token": token}, function(err, user){ // findOne :: mongoDB에 이미 있는 method
// 에러가 나면
if(err) return cb(err);
// 에러가 안나면
cb(null, user)
})
})
// 2. 유저 아이디를 이용해서 유저를 찾은 다음에 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인.
user.findOne({"_id": decoded, "token": token}, function(err, user){ // findOne :: mongoDB에 이미 있는 method
// 에러가 나면
if(err) return cb(err);
// 에러가 안나면
cb(null, user)
})
})
}
// 만든 스키마를 모델로 감싸줌
const User = mongoose.model('User', userSchema)
const User = mongoose.model('User', userSchema);
// 이 모델을 다른 파일에서도 쓰고싶으면 아래와 같이 해주면 됨
module.exports = {User}
\ No newline at end of file
module.exports = { User }
\ No newline at end of file
......
const express = require('express');
const { User } = require("../models/User");
const { auth } = require("../middleware/auth");
const router = express.Router();
router.get("/auth", auth, (req, res) => {
res.status(200).json({
_id: req.user._id,
isAdmin: req.user.role === 0 ? false : true,
isAuth: true,
email: req.user.email,
name: req.user.name,
lastname: req.user.lastname,
role: req.user.role,
image: req.user.image,
});
});
router.post("/register", (req, res) => {
const user = new User(req.body);
user.save((err, doc) => {
if (err) return res.json({ success: false, err });
return res.status(200).json({
success: true
});
});
});
router.post("/login", (req, res) => {
User.findOne({ email: req.body.email }, (err, user) => {
if (!user)
return res.json({
loginSuccess: false,
message: "Auth failed, email not found"
});
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({ loginSuccess: false, message: "Wrong password" });
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
res.cookie("w_authExp", user.tokenExp);
res
.cookie("w_auth", user.token)
.status(200)
.json({
loginSuccess: true, userId: user._id
});
});
});
});
});
router.get("/logout", auth, (req, res) => {
User.findOneAndUpdate({ _id: req.user._id }, { token: "", tokenExp: "" }, (err, doc) => {
if (err) return res.json({ success: false, err });
return res.status(200).send({
success: true
});
});
});
module.exports = router;