정승호

사진 업로드 작업 완료

Showing 56 changed files with 1645 additions and 540 deletions
# HEXA for you 개편 프로젝트
>20151039045 정승호
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## 개요
현재 경희대 연행분과 클래식 기타 동아리 Hexa는 Hexa for you 라는 안드로이드 어플리케이션을 통하여 행사 사진을 업로드 하고 있는 중이다, 하지만, 앱의 기능이 사진만 보여주는 것임에도 앱 내에 모든 사진 자료들을 넣어서 앱의 용량이 300Mb 정도로 매우 크게 되어있다, 또한 제공 플랫폼이 안드로이드 하나뿐이어서 다른 플랫폼 유저들을 앱을 사용할 수 없다는 단점이 존재한다, 이러한 단점들을 개선하기 위해 동아리의 행사 사진들을 보관할 수 있는 웹페이지를 만들어서 좀 더 관리하기 쉽게 만들도록 하겠다.
## 사용기술
### 1. Node.js
- 웹사이트를 구동하기 위한 서버 사이드 파트는 Node.js로 작성할 예정이다.
## Available Scripts
### 2. AWS
- 작성한 코드를 AWS에서 구동을 할 예정
In the project directory, you can run:
### `npm start`
### 3. MongoDB
- 기본적으로 사진을 저장하는 웹사이트기 때문에 데이터베이스의 사용이 필요하고, 카카오 신한은행등 많은 기업들이 채택하는 중인 MongoDB를 이용해 사진을 관리
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
### 4. React.js
- 웹사이트의 프론트엔드 부분을 다룰것이며, 깔끔한 UI를 구현하기 위해 사용
\ No newline at end of file
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.<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.<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.<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.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
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.
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.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
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
### 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
### 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
### `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
......
......@@ -6,23 +6,23 @@ In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
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.<br />
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.<br />
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.<br />
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.<br />
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.
......@@ -33,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.
......
This diff could not be displayed because it is too large.
......@@ -3,20 +3,24 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"antd": "^4.3.4",
"antd": "^3.24.1",
"axios": "^0.19.2",
"http-proxy-middleware": "^1.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"core-js": "^3.6.4",
"formik": "^1.5.8",
"moment": "^2.24.0",
"react": "^16.8.6",
"react-app-polyfill": "^1.0.6",
"react-dom": "^16.8.6",
"react-dropzone": "^11.0.1",
"react-icons": "^3.7.0",
"react-image-gallery": "^1.0.7",
"react-redux": "^7.1.0-rc.1",
"react-router-dom": "^5.0.1",
"react-scripts": "3.4.1",
"redux": "^4.0.5",
"redux": "^4.0.0",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.3.0"
"redux-thunk": "^2.3.0",
"yup": "^0.27.0"
},
"scripts": {
"start": "react-scripts start",
......@@ -38,5 +42,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"http-proxy-middleware": "^1.0.3"
}
}
......
No preview for this file type
......@@ -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": ".",
......
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import LandingPage from './components/views/LandingPage/LandingPage';
import LoginPage from './components/views/LoginPage/LoginPage';
import RegisterPage from './components/views/RegisterPage/RegisterPage';
function App() {
return (
<Router>
<div>
{/*
A <Switch> looks through all its children <Route>
elements and renders the first one whose path
matches the current URL. Use a <Switch> any time
you have multiple routes, but you want only one
of them to render at a time
*/}
<Switch>
<Route exact path="/" component={LandingPage}/>
<Route exact path="/login" component={LoginPage}/>
<Route exact path="/register" component = {RegisterPage} />
</Switch>
</div>
</Router>
);
}
export default App
export const LOGIN_USER = "login_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
LOGIN_USER,
REGISTER_USER,
AUTH_USER,
LOGOUT_USER,
} from './types';
import { USER_SERVER } from '../components/Config.js';
export function registerUser(dataToSubmit){
const request = axios.post(`${USER_SERVER}/register`,dataToSubmit)
.then(response => response.data);
return {
type: REGISTER_USER,
payload: request
}
}
export function loginUser(dataTosubmit){
const request = axios.post('/api/users/login', dataTosubmit)
.then(response => response.data)
export function loginUser(dataToSubmit){
const request = axios.post(`${USER_SERVER}/login`,dataToSubmit)
.then(response => response.data);
return {
type: LOGIN_USER,
payload: request
}
}
\ No newline at end of file
}
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
}
}
......
import {combineReducers} from 'redux';
import { combineReducers } from 'redux';
import user from './user_reducer';
const rootReducer = combineReducers({
user
})
user,
});
export default rootReducer
\ No newline at end of file
export default rootReducer;
\ No newline at end of file
......
import {
LOGIN_USER
LOGIN_USER,
REGISTER_USER,
AUTH_USER,
LOGOUT_USER,
} from '../_actions/types';
export default function (state={}, action){
switch (action.type) {
export default function(state={},action){
switch(action.type){
case REGISTER_USER:
return {...state, register: action.payload }
case LOGIN_USER:
return { ...state, loginSuccess: action.payload}
break;
return { ...state, loginSucces: action.payload }
case AUTH_USER:
return {...state, userData: action.payload }
case LOGOUT_USER:
return {...state }
default:
return state;
}
......
import React, { Suspense } from 'react';
import { Route, Switch } from "react-router-dom";
import Auth from "../hoc/auth";
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";
import UploadProductPage from "./views/UploadProductPage/UploadProductPage.js";
import DetailProductPage from "./views/DetailProductPage/DetailProductPage.js";
//null 누구나 들어갈 수 있음
//true 로그인 하면 들어갈 수 있음
//false 로그인 유저는 못들어감
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)} />
<Route exact path="/product/upload" component={Auth(UploadProductPage, true)} />
<Route exact path="/product/:productId" component={Auth(DetailProductPage, null)} />
</Switch>
</div>
<Footer />
</Suspense>
);
}
export default App;
//서버 라우트
export const USER_SERVER = '/api/users';
import React, { useState } from 'react'
import Dropzone from 'react-dropzone'
import { Icon } from 'antd';
import axios from 'axios';
function FileUpload(props) {
const [Images, setImages] = useState([])
const dropHandler = (files) => {
let formData = new FormData();
const config = {
header: { 'content-type': 'multipart/fomr-data' }
}
formData.append("file", files[0])
axios.post('/api/product/image', formData, config)
.then(response => {
if (response.data.success) {
setImages([...Images, response.data.filePath])
props.refreshFunction([...Images, response.data.filePath])
} else {
alert('파일을 저장하는데 실패했습니다.')
}
})
}
// 이미지 클릭 시 삭제 기능 추가
const deleteHandler = (image) => {
const currentIndex = Images.indexOf(image);
let newImages = [...Images]
newImages.splice(currentIndex, 1)
setImages(newImages)
props.refreshFunction(newImages)
}
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Dropzone onDrop={dropHandler}>
{({ getRootProps, getInputProps }) => (
<div
style={{
width: 300, height: 240, border: '1px solid lightgray',
display: 'flex', alignItems: 'center', justifyContent: 'center'
}}
{...getRootProps()}>
<input {...getInputProps()} />
<Icon type="plus" style={{ fontSize: '3rem' }} />
</div>
)}
</Dropzone>
<div style={{ display: 'flex', width: '350px', height: '240px', overflowX: 'scroll' }}>
{Images.map((image, index) => (
<div onClick={() => deleteHandler(image)} key={index}>
<img style={{ minWidth: '300px', width: '300px', height: '240px' }}
src={`http://localhost:5000/${image}`}
/>
</div>
))}
</div>
</div>
)
}
export default FileUpload;
\ No newline at end of file
import React from 'react'
import { Icon, Col, Card, Row, Carousel } from 'antd';
function ImageSlider(props) {
return (
<div>
<Carousel autoplay >
{props.images.map((image, index) => (
<div key={index}>
<img style={{ width: '100%', maxHeight: '150px' }}
src={`http://localhost:5000/${image}`} />
</div>
))}
</Carousel>
</div>
)
}
export default ImageSlider
\ No newline at end of file
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import ProductImage from './Sections/ProductImage';
import ProductInfo from './Sections/ProductInfo';
import { Row, Col } from 'antd';
function DetailProductPage(props) {
const productId = props.match.params.productId
const [Product, setProduct] = useState({})
useEffect(() => {
axios.get(`/api/product/products_by_id?id=${productId}&type=single`)
.then(response => {
setProduct(response.data[0])
})
.catch(err => alert(err))
}, [])
return (
<div style={{ width: '100%', padding: '3rem 4rem' }}>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<h1>{Product.title}</h1>
</div>
<br />
<Row gutter={[16, 16]} >
<Col lg={12} sm={24}>
{/* ProductImage */}
<ProductImage detail={Product} />
</Col>
<Col lg={12} sm={24}>
{/* ProductInfo */}
<ProductInfo detail={Product} />
</Col>
</Row>
</div>
)
}
export default DetailProductPage
import React, { useState, useEffect } from 'react'
import ImageGallery from 'react-image-gallery';
function ProductImage(props) {
const [Images, setImages] = useState([])
useEffect(() => {
if (props.detail.images && props.detail.images.length > 0) {
let images = []
props.detail.images.map(item => {
images.push({
original: `http://localhost:5000/${item}`,
thumbnail: `http://localhost:5000/${item}`
})
})
setImages(images)
}
}, [props.detail])
return (
<div>
<ImageGallery items={Images} />
</div>
)
}
export default ProductImage
import React from 'react'
import {Descriptions } from 'antd';
function ProductInfo(props) {
return (
<div>
<Descriptions title="행사 정보">
<Descriptions.Item label="Events">{props.detail.continent}</Descriptions.Item>
<Descriptions.Item label="View">{props.detail.views}</Descriptions.Item>
<Descriptions.Item label="Description">{props.detail.description}</Descriptions.Item>
</Descriptions>
<br />
<br />
<br />
</div>
)
}
export default ProductInfo
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> 경희대 클래식기타 동아리 HEXA </p>
</div>
)
}
......
import React, {useEffect} from 'react'
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { FaCode } from "react-icons/fa";
import axios from "axios";
import { Icon, Col, Card, Row, Carousel } from 'antd';
import Meta from 'antd/lib/card/Meta';
import ImageSlider from '../../utils/ImageSlider';
function LandingPage() {
const [Products, setProducts] = useState([])
const [Skip, setSkip] = useState(0)
const [Limit, setLimit] = useState(8)
const [PostSize, setPostSize] = useState(0)
useEffect(() => {
axios.get('/api/hello')
.then(response => {console.log(response)})
let body = {
skip: Skip,
limit: Limit
}
getProducts(body)
}, [])
const getProducts = (body) => {
axios.post('/api/product/products', body)
.then(response => {
if (response.data.success) {
if (body.loadMore) {
setProducts([...Products, ...response.data.productInfo])
} else {
setProducts(response.data.productInfo)
}
setPostSize(response.data.postSize)
} else {
alert(" 상품들을 가져오는데 실패 했습니다.")
}
})
}
const loadMoreHanlder = () => {
let skip = Skip + Limit
let body = {
skip: skip,
limit: Limit,
loadMore: true
}
getProducts(body)
setSkip(skip)
}
const renderCards = Products.map((product, index)=>{
return <Col lg ={6} md = {8} xs={24}>
<Card
key={index}
cover = {<a href={`/product/${product._id}`} ><ImageSlider images={product.images} /></a>}
>
<Meta
title={product.title}
description={`${product.description}`}
/>
</Card>
</Col>
})
return (
<div style = {{
display: 'flex', justifyContent: 'center', alignItems: 'center'
, width: '100%', height: '100vh'
}}>
<h2> 시작 페이지 </h2>
<div style = {{ width: '75%', margin: '3rem auto' }}>
<div style={{ textAlign: 'center' }}>
<h2>행사 사진 <Icon type = "star"/></h2>
</div>
<Row gutter = {16}>
{renderCards}
</Row>
{PostSize >= Limit &&
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button onClick={loadMoreHanlder}>더보기</button>
</div>
}
</div>
)
}
......
import React,{useState} from 'react'
import Axios from 'axios'
import { useDispatch} from 'react-redux';
import {loginUser} from '../../../_actions/user_actions';
function LoginPage(){
const dispatch = useDispatch();
const [Email, setEmail] = useState("")
const [PassWord, setPassWord] = useState("")
const onEmailHandler = (event) => {
setEmail(event.currentTarget.value)
}
const onPassWordHandler = (event) => {
setPassWord(event.currentTarget.value)
}
const onSubmitHandler = (event) => {
event.preventDefault();
let body = {
email: Email,
password: PassWord
}
dispatch(loginUser(body))
}
return (
<div style = {{
display: 'flex', justifyContent: 'center', alignItems: 'center'
, width: '100%', height: '100vh'
}}>
<form style = {{display :'flex', flexDirection: 'column'}}
onSubmit= {onSubmitHandler}
>
<label>Email</label>
<input type = "email" value = {Email} onChange={onEmailHandler} />
<label>PassWord</label>
<input type = "password" value= {PassWord} onChange = {onPassWordHandler} />
<br />
<button type = "submit">
Login
</button>
</form>
</div>
)
}
export default LoginPage
\ No newline at end of file
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 [formErrorMessage, setFormErrorMessage] = useState('')
const [rememberMe, setRememberMe] = useState(rememberMeChecked)
const handleRememberMe = () => {
setRememberMe(!rememberMe)
};
const initialEmail = localStorage.getItem("rememberMe") ? localStorage.getItem("rememberMe") : '';
return (
<Formik
initialValues={{
email: initialEmail,
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
};
dispatch(loginUser(dataToSubmit))
.then(response => {
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 {
setFormErrorMessage('Check out your Account or Password again')
}
})
.catch(err => {
setFormErrorMessage('Check out your Account or Password again')
setTimeout(() => {
setFormErrorMessage("")
}, 3000);
});
setSubmitting(false);
}, 500);
}}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<div className="app">
<Title level={2}>Log In</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>
<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>
{formErrorMessage && (
<label ><p style={{ color: '#ff0000bf', fontSize: '0.7rem', border: '1px solid', padding: '1rem', borderRadius: '10px' }}>{formErrorMessage}</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}>
Log in
</Button>
</div>
Or <a href="/register">register now!</a>
</Form.Item>
</form>
</div>
);
}}
</Formik>
);
};
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
</div>
)
function 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="/">Hexaforyou</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
\ No newline at end of file
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;
}
}
\ 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(`${USER_SERVER}/logout`).then(response => {
if (response.status === 200) {
props.history.push("/login");
} else {
alert('Log Out Failed')
}
});
};
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="upload">
<a href = "/product/upload">Upload</a>
</Menu.Item>
<Menu.Item key="logout">
<a onClick={logoutHandler}>Logout</a>
</Menu.Item>
</Menu>
)
}
}
export default withRouter(RightMenu);
import React from 'react'
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(){
return (
<div>
RegisterPage
</div>
)
import {
Form,
Input,
Button,
} from 'antd';
}
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,
},
},
};
export default RegisterPage
\ No newline at end of file
function RegisterPage(props) {
const dispatch = useDispatch();
return (
<Formik
initialValues={{
email: '',
lastName: '',
name: '',
password: '',
confirmPassword: ''
}}
validationSchema={Yup.object().shape({
name: Yup.string()
.required('Name is required'),
lastName: Yup.string()
.required('Last Name is required'),
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'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], 'Passwords must match')
.required('Confirm Password is required')
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
let dataToSubmit = {
email: values.email,
password: values.password,
name: values.name,
lastname: values.lastname,
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)
}
})
setSubmitting(false);
}, 500);
}}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<div className="app">
<h2>Sign up</h2>
<Form style={{ minWidth: '375px' }} {...formItemLayout} onSubmit={handleSubmit} >
<Form.Item required label="Name">
<Input
id="name"
placeholder="Enter your name"
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>
<Form.Item required label="Last Name">
<Input
id="lastName"
placeholder="Enter your Last Name"
type="text"
value={values.lastName}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.lastName && touched.lastName ? 'text-input error' : 'text-input'
}
/>
{errors.lastName && touched.lastName && (
<div className="input-feedback">{errors.lastName}</div>
)}
</Form.Item>
<Form.Item required label="Email" hasFeedback validateStatus={errors.email && touched.email ? "error" : 'success'}>
<Input
id="email"
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>
<Form.Item required label="Password" hasFeedback validateStatus={errors.password && touched.password ? "error" : 'success'}>
<Input
id="password"
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>
<Form.Item required label="Confirm" hasFeedback>
<Input
id="confirmPassword"
placeholder="Enter your confirmPassword"
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>
<Form.Item {...tailFormItemLayout}>
<Button onClick={handleSubmit} type="primary" disabled={isSubmitting}>
Submit
</Button>
</Form.Item>
</Form>
</div>
);
}}
</Formik>
);
};
export default RegisterPage
......
import React, { useState } from 'react'
import { Typography, Button, Form, Input } from 'antd';
import FileUpload from '../../utils/FileUpload';
import Axios from 'axios';
const { TextArea } = Input;
const Continents = [
{ key: 1, value: "개강총회" },
{ key: 2, value: "종강총회" },
{ key: 3, value: "창립제" },
{ key: 4, value: "신입생환영연주회" },
{ key: 5, value: "신입생연주회" },
{ key: 6, value: "정기연주회" },
{ key: 7, value: "기타행사" }
]
// 업로드 페이지
export default function UploadProductPage(props) {
const [Title, setTitle] = useState("")
const [Description, setDescription] = useState("")
const [Continent, setContinent] = useState(1)
const [Images, setImages] = useState([])
const titleChangeHandler = (event) => {
setTitle(event.currentTarget.value)
}
const descriptionChangeHandler = (event) => {
setDescription(event.currentTarget.value)
}
const continentChangeHandler = (event) => {
setContinent(event.currentTarget.value)
}
const updateImages = (newImages) => {
setImages(newImages)
}
const submitHandler = (event) => {
event.preventDefault();
if (!Title || !Description || !Continent || !Images) {
return alert(" 모든 값을 넣어주셔야 합니다.")
}
//서버에 채운 값들을 request로 보낸다.
const body = {
//로그인 된 사람의 ID
writer: props.user.userData._id,
title: Title,
description: Description,
images: Images,
continents: Continent
}
Axios.post('/api/product', body)
.then(response => {
if (response.data.success) {
alert('사진 업로드에 성공 했습니다.')
props.history.push('/')
} else {
alert('사진 업로드에 실패 했습니다.')
}
})
}
return (
<div style={{ maxWidth: '700px', margin: '2rem auto' }}>
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
<h2> 행사 사진 업로드</h2>
</div>
<Form onSubmit={submitHandler}>
{/* DropZone */}
<FileUpload refreshFunction={updateImages} />
<br />
<br />
<label>제목</label>
<Input onChange={titleChangeHandler} value={Title} />
<br />
<br />
<label>설명</label>
<TextArea onChange={descriptionChangeHandler} value={Description} />
<br />
<br />
<select onChange={continentChangeHandler} value={Continent}>
{Continents.map(item => (
<option key={item.key} value={item.key}> {item.value}</option>
))}
</select>
<br />
<br />
<button type="submit">
확인
</button>
</Form>
</div>
)
}
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(() => {
// 현재 나의 상태를 알기 위해서 Auth request보냄
dispatch(auth()).then(response => {
// 로그인 상태가 아님
if (!response.payload.isAuth) {
if (option) {
props.history.push('/login')
}
// 로그인 상태
} else {
if (adminRoute && !response.payload.isAdmin) {
props.history.push('/')
}
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 App from './components/App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import 'antd/dist/antd.css';
import { applyMiddleware, createStore} from 'redux';
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/index';
const creatStoreWithMiddleware = applyMiddleware(promiseMiddleware,ReduxThunk)(createStore)
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore);
ReactDOM.render(
<Provider
store={creatStoreWithMiddleware(Reducer,
window.__REDUX_DEVTOOLS_EXTENTION__&&
window.__REDUX_DEVTOOLS_EXTENTION__()
<Provider
store={createStoreWithMiddleware(
Reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)}
>
<App />
</Provider>
,document.getElementById('root')
);
>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, 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.0/8 are considered localhost for IPv4.
// 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}$/
)
......@@ -22,12 +11,9 @@ const isLocalhost = Boolean(
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;
}
......@@ -35,11 +21,10 @@ export function register(config) {
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 ' +
......@@ -47,7 +32,7 @@ export function register(config) {
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
......@@ -66,25 +51,19 @@ function registerValidSW(swUrl, config) {
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);
}
......@@ -99,25 +78,23 @@ function registerValidSW(swUrl, config) {
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
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);
}
})
......@@ -130,12 +107,8 @@ function checkValidServiceWorker(swUrl, config) {
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
......
const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = function(app){
const { createProxyMiddleware } = require('http-proxy-middleware');
// 프록시 서버 만들어 줘서 프로트와 서버의 데이터 통신을 가능하게 해줌
module.exports = function (app) {
app.use(
createProxyMiddleware('/api',{
target : 'http://localhost:5000/',
changeOrigin: true
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
)
};
);
};
\ No newline at end of file
......
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
{
"name": "hexa_for_you_renewal",
"version": "1.0.0",
"description": ">20151039045 정승호",
"description": "react boiler plate",
"main": "index.js",
"engine": {
"node": "10.16.0",
"npm": "6.9.0"
},
"scripts": {
"start": "node index.js",
"start": "node server/index.js",
"backend": "nodemon server/index.js",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "concurrently \"npm run backend \" \"npm run start --prefix client\""
"frontend": "npm run start --prefix client",
"dev": "concurrently \"npm run backend\" \"npm run start --prefix client\""
},
"repository": {
"type": "git",
......@@ -16,15 +20,22 @@
"author": "seungho",
"license": "ISC",
"dependencies": {
"bcrypt": "^4.0.1",
"body-parser": "^1.19.0",
"concurrently": "^5.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.9.15"
"moment": "^2.24.0",
"mongoose": "^5.4.20",
"multer": "^1.4.2",
"react-redux": "^5.0.7",
"saslprep": "^1.0.3",
"supports-color": "^7.1.0"
},
"devDependencies": {
"nodemon": "^2.0.4"
"concurrently": "^4.1.0",
"nodemon": "^1.19.1"
}
}
......
if(process.env.NODE_ENV=== 'production'){
module.exports = require('./prod')
} else{
module.exports = require('./dev')
if (process.env.NODE_ENV === 'production') {
module.exports = require('./prod');
} else {
module.exports = require('./dev');
}
\ No newline at end of file
......
module.exports = {
mongoURI : process.env.MONGO_URI
mongoURI:process.env.MONGO_URI
}
\ No newline at end of file
......
const express = require("express");
const app = express();
const path = require("path");
const cors = require('cors')
const express = require('express')
const app = express()
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const port = 5000
const { auth } = require('./middleware/auth');
const { User } = require('./models/Users');
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const config = require('./config/dev')
const config = require("./config/key");
app.use(bodyParser.urlencoded({extended : true}));
app.use(bodyParser.json());
const mongoose = require("mongoose");
const connect = mongoose.connect(config.mongoURI,
{
useNewUrlParser: true, useUnifiedTopology: true,
useCreateIndex: true, useFindAndModify: false
})
.then(() => console.log('MongoDB Connected...'))
.catch(err => console.log(err));
app.use(cors())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
const mongoose = require('mongoose')
mongoose.connect(config.mongoURI,{
useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex : true, useFindAndModify: false
}).then(() => console.log('MongoDb connected....'))
.catch(err => console.log('Error'))
app.get('/api/hello', (req,res) => res.send('Hello world!! 오늘도 지식이 쌓였당!!'))
app.post('/api/users/register', (req, res) => {
// 회원 가입시 필요한 정보들을 client에서 가져오면
// 그것들을 데이터베이스에 넣어준다.
const user = new User(req.body)
user.save((err, userInfo) => {
if(err) return res.json({success : false, err})
return res.status(200).json({
success : true
})
}) // MongoDb에 저징
})
app.post('/api/users/login', (req, res) => {
// 요청된 이메일이 데이터 베이스에 있는지 확인
User.findOne({email : req.body.email}, (err, user) =>{
if(!user){
return res.json({
loginSuccess : false,
message : "이메일에 해당하는 유저가 없습니다."
})
}
// 요청된 이메일이 데이터 베이스에 있다면, 비밀번호가 맞는디 확인
user.comparePassword( req.body.password,(err, isMatch) =>{
if(!isMatch)
return res.json({loginSuccess : false, message : "비밀번호 오류"})
// 비밀번호까지 맞다면 그 유저에 대한 토큰을 생성한다.
user.generateToken((err,user) =>{
if(err) return res.status(400).send(err);
//토큰을 저장한다.
res.cookie("x_auth",user.token)
.status(200)
.json({loginSuccess:true, userId: user._id})
})
})
})
})
app.get('/api/users/auth', auth , (req,res)=> {
// 인증이 완료
res.status(200).json({
_id: req.user._id,
isAdmin: req.user.role === 0 ? false : true,
isAuth: true,
email: req.user.name,
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},
{ token: ""}
, (err, user) =>{
if(err) return res.json({success: false, err});
return res.status(200).send({
success: true
})
})
})
app.listen(port, () => console.log('example app listen on port %s!', port))
app.use('/api/users', require('./routes/users'));
app.use('/api/product', require('./routes/product'));
app.use('/uploads', express.static('uploads'));
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"));
});
}
const port = process.env.PORT || 5000
app.listen(port, () => {
console.log(`Server Listening on ${port}`)
});
\ No newline at end of file
......
const {User} = require('../models/Users');
let auth = (req,res,next) => {
//인증처리 하는 함수
// 클라이언트에서 cookie 가져온다
let token = req.cookies.x_auth;
// 토큰을 복호화 한 후 유저 찾는다.
User.findByToken(token, (err, user)=>{
if(err) throw err;
if(!err) return res.json({isAuth :false, error : true})
req.token = token;
req.user = user;
next();
})
// 유저 없으면 인증 됨
// 유저 있으면 인증 안됨
}
module.exports = {auth}
\ No newline at end of file
const { User } = require('../models/User');
let auth = (req, res, next) => {
let token = req.cookies.w_auth;
User.findByToken(token, (err, user) => {
if (err) throw err;
if (!user)
return res.json({
isAuth: false,
error: true
});
req.token = token;
req.user = user;
next();
});
};
module.exports = { auth };
......
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const productSchema = mongoose.Schema({
writer: {
type: Schema.Types.ObjectId,
ref: 'User'
},
title: {
type: String,
maxlength: 50
},
description: {
type: String,
},
images: {
type: Array,
default: []
},
eontinents: {
type: Number,
default: 1
},
views: {
type: Number,
default: 0
}
}, { timestamps: true })
productSchema.index({
title: 'text',
description: 'text'
}, {
weights: {
title: 5,
description: 1
}
})
const Product = mongoose.model('Product', productSchema);
module.exports = { Product }
\ No newline at end of file
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const jwt = require('jsonwebtoken');
const moment = require("moment");
const userSchema = mongoose.Schema({
name: {
type:String,
maxlength:50
},
email: {
type:String,
trim:true,
unique: 1
},
password: {
type: String,
minglength: 5
},
lastname: {
type:String,
maxlength: 50
},
role : {
type:Number,
default: 0
},
image: String,
token : {
type: String,
},
tokenExp :{
type: Number
}
})
userSchema.pre('save', function( next ) {
var user = this;
if(user.isModified('password')){
// console.log('password changed')
bcrypt.genSalt(saltRounds, function(err, salt){
if(err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash){
if(err) return next(err);
user.password = hash
next()
})
})
} else {
next()
}
});
userSchema.methods.comparePassword = function(plainPassword,cb){
bcrypt.compare(plainPassword, this.password, function(err, isMatch){
if (err) return cb(err);
cb(null, isMatch)
})
}
userSchema.methods.generateToken = function(cb) {
var user = this;
var token = jwt.sign(user._id.toHexString(),'secret')
var oneHour = moment().add(1, 'hour').valueOf();
user.tokenExp = oneHour;
user.token = token;
user.save(function (err, user){
if(err) return cb(err)
cb(null, user);
})
}
userSchema.statics.findByToken = function (token, cb) {
var user = this;
jwt.verify(token,'secret',function(err, decode){
user.findOne({"_id":decode, "token":token}, function(err, user){
if(err) return cb(err);
cb(null, user);
})
})
}
const User = mongoose.model('User', userSchema);
module.exports = { User }
\ No newline at end of file
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const jwt = require('jsonwebtoken');
const userSchema = mongoose.Schema({
name : {
type : String,
maxlength : 50
},
email :{
type : String,
trim : true, // trim은 이메일 주소 받을때 공백을 없애준다.
unique : 1 // 중복 허용 안함
},
password :{
type : String,
maxlength : 50
},
lastname:{
type : String,
maxlength : 50
},
role:{
type : Number, // 1 : admin, 0 : common user
default : 0
},
image: String,
token: {
type : String
},
tokenExp :{
type: Number
}
})
// password 암호화
userSchema.pre('save', function(next){
var user = this;
if(user.isModified('password')){
bcrypt.genSalt(saltRounds, function(err, salt){
if(err)return next(err)
bcrypt.hash(user.password, salt, function(err, hash){
if(err) return next(err)
user.password = hash
//hash 값으로 변경해서 저장
next()
})
})
} else{
next() // 비밀번호를 바꾸는 것이 아니라면, 넘어감
}
})
userSchema.methods.comparePassword = function(plainPassword, cb){
bcrypt.compare(plainPassword, this.password, function(err, isMatch){
if(err) return cb(err),
cb(null, isMatch)
})
}
userSchema.methods.generateToken = function(cb){
// token생성
var user = this;
var token = jwt.sign(user._id, 'secretToken')
user.token = token
user.save(function(err, user){
if(err) return cb(err)
cb(null, user)
})
}
userSchema.statics.findByToken = function(token, cb){
var user = this;
// 토큰을 복호화한다.
jwt.verify(token,'secretToken', function(err, decoded){
// 유저 아이디를 이용해서 유저를 찾고
// 클라이언트에서 가져온 토큰과 데이터베이스에 보관된 토큰이
//일치하는지 확인
user.findOne({"_id": decoded, "token" : token}, function(err, user){
if(err) return cb(err);
cb(null, user)
})
})
}
const User = mongoose.model('Users', userSchema)
module.exports = {User}
\ No newline at end of file
const express = require('express');
const router = express.Router();
const multer = require('multer');
const { Product } = require("../models/Product");
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/')
},
filename: function (req, file, cb) {
cb(null, `${Date.now()}_${file.originalname}`)
}
})
var upload = multer({ storage: storage }).single("file")
router.post('/image', (req, res) => {
//가져온 이미지를 저장을 해주면 된다.
upload(req, res, err => {
if (err) {
return req.json({ success: false, err })
}
return res.json({ success: true, filePath: res.req.file.path, fileName: res.req.file.filename })
})
})
router.post('/', (req, res) => {
//받아온 정보들을 DB에 넣어 준다.
const product = new Product(req.body)
product.save((err) => {
if (err) return res.status(400).json({ success: false, err })
return res.status(200).json({ success: true })
})
})
router.post('/products', (req, res) => {
// 정보 가져오기
let limit = req.body.limit ? parseInt(req.body.limit) : 20;
let skip = req.body.skip ? parseInt(req.body.skip) : 0;
Product.find()
.populate("writer")
.skip(skip)
.limit(limit)
.exec((err, productInfo)=> {
if(err) return res.status(400).json({success:false, err})
return res.status(200).json({success:true, productInfo})
})
})
router.post('/', (req, res) => {
//받아온 정보들을 DB에 넣어 준다.
const product = new Product(req.body)
product.save((err) => {
if (err) return res.status(400).json({ success: false, err })
return res.status(200).json({ success: true })
})
})
router.get('/products_by_id', (req, res) => {
let type = req.query.type
let productIds = req.query.id
if (type === "array") {
//id=123123123,324234234,324234234 이거를
//productIds = ['123123123', '324234234', '324234234'] 이런식으로 바꿔주기
let ids = req.query.id.split(',')
productIds = ids.map(item => {
return item
})
}
//productId를 이용해서 DB에서 productId와 같은 상품의 정보를 가져온다.
Product.find({ _id: { $in: productIds } })
.populate('writer')
.exec((err, product) => {
if (err) return res.status(400).send(err)
return res.status(200).send(product)
})
})
module.exports = router;
const express = require('express');
const router = express.Router();
const { User } = require("../models/User");
const { auth } = require("../middleware/auth");
//=================================
// User
//=================================
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;