From 949b2acbb5c43a5f7a2c2f482c863088dc7893ff Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 01:33:48 +0900 Subject: [PATCH 01/28] Chore backend, frontend package add axios to frontend add cors, cookie-parser, jsonwebtoken, passport, passport-github to backend --- backend/package-lock.json | 159 +++++++++++++++++++++++++++++++++++++ backend/package.json | 8 +- frontend/package-lock.json | 8 ++ frontend/package.json | 1 + 4 files changed, 174 insertions(+), 2 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index a6353923..542a3c0a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1277,6 +1277,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1449,6 +1454,11 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1716,6 +1726,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1825,6 +1844,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2938,6 +2965,49 @@ "minimist": "^1.2.5" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", @@ -3012,6 +3082,41 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3292,6 +3397,16 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" }, + "oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -3446,6 +3561,40 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-github": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz", + "integrity": "sha1-jOHj/NYa11eOsd9ZWDnkrqEjVdQ=", + "requires": { + "passport-oauth2": "1.x.x" + } + }, + "passport-oauth2": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz", + "integrity": "sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==", + "requires": { + "base64url": "3.x.x", + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -3490,6 +3639,11 @@ } } }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -4303,6 +4457,11 @@ "is-typedarray": "^1.0.0" } }, + "uid2": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz", + "integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=" + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", diff --git a/backend/package.json b/backend/package.json index 8c075d77..3ef6d20d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,14 +19,18 @@ "homepage": "https://github.com/boostcamp-2020/Project12-C-Slack-Web", "dependencies": { "concurrently": "^5.3.0", - "cookie-parser": "~1.4.4", + "cookie-parser": "^1.4.5", "core-js": "^3.6.5", + "cors": "^2.8.5", "debug": "~2.6.9", "dotenv": "^8.2.0", "express": "~4.16.1", + "jsonwebtoken": "^8.5.1", "mongoose": "^5.10.15", "morgan": "~1.9.1", - "nodemon": "^2.0.4" + "nodemon": "^2.0.4", + "passport": "^0.4.1", + "passport-github": "^1.1.0" }, "devDependencies": { "@babel/core": "^7.12.8", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b475e908..5da25318 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5484,6 +5484,14 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz", "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==" }, + "axios": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8fd6d15f..dd154482 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "axios": "^0.21.0", "react": "^17.0.1", "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", From 22746593fb1292b7485fa3c88255d3235b4bef8f Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 01:43:10 +0900 Subject: [PATCH 02/28] Feat: github login api add github login api --- backend/app.js | 14 +++++- backend/config/passport.js | 64 ++++++++++++++++++++++++++++ backend/controller/index.js | 5 +++ backend/controller/userController.js | 48 +++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 backend/config/passport.js create mode 100644 backend/controller/userController.js diff --git a/backend/app.js b/backend/app.js index 2278f175..9cb02e14 100644 --- a/backend/app.js +++ b/backend/app.js @@ -6,6 +6,10 @@ import mongoose from 'mongoose' import controller from './controller' import statusCode from './util/statusCode' import resMessage from './util/resMessage' +import passport from 'passport' +import passportConfig from './config/passport' +const cors = require('cors') + require('dotenv').config() const app = express() @@ -23,8 +27,16 @@ mongoose app.use(logger('dev')) app.use(express.json()) app.use(express.urlencoded({ extended: false })) -app.use(cookieParser()) app.use(express.static(path.join(__dirname, '../dist'))) +app.use(cookieParser(process.env.COOKIE_SECRET)) +app.use(passport.initialize()) +app.use( + cors({ + origin: true, + credentials: true, + }), +) +passportConfig() app.use('/api', controller) app.use('/docs', express.static(path.join(__dirname, './docs'))) diff --git a/backend/config/passport.js b/backend/config/passport.js new file mode 100644 index 00000000..346c9366 --- /dev/null +++ b/backend/config/passport.js @@ -0,0 +1,64 @@ +const passport = require('passport') +const GitHubStrategy = require('passport-github').Strategy + +require('dotenv').config() + +const githubStrategyOption = { + clientID: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackURL: process.env.GITHUB_CALLBACK_URL, +} + +async function gitStrategyLogin(profiles) { + //git 정보가 db에 있는지 확인 + // let user = await userDao.getUser(profiles.username) + // //db에 저장이 안 되어있을 경우 새로 db에 저장 + // if (user === null) { + // try { + // const id = profiles.username + // const profile = profiles.photos[0].value + // const password = 'github' + + // // const result = await userDao.insertUser(id, password, profile) + // } catch (e) { + // return { + // success: false, + // } + // } + // } else if (user !== null && user.password !== 'github') { + // console.log('깃헙아님', user.password) + // //깃헙으로 가입하지 않은 아이디로 이미 가입되어져 있는 경우 오류 처리 + // return { + // success: false, + // } + // } + + // return { + // success: true, + // userId: profiles.username, + // profile: profiles.photos[0].value, + // } + return { + success: true, + userId: 'rockpell', + profile: 'a', + } +} + +async function githubVerify(accessToken, refreshToken, profile, done) { + try { + const result = await gitStrategyLogin(profile) + const user = { userId: result.userId, profile: result.profile } + + if (result.success) { + return done(null, user) + } + return done(null, false, { message: '깃허브 로그인에 실패했습니다.' }) + } catch (err) { + return done(null, false, { message: 'GitHub verify err 발생' }) + } +} + +module.exports = () => { + passport.use(new GitHubStrategy(githubStrategyOption, githubVerify)) +} diff --git a/backend/controller/index.js b/backend/controller/index.js index 58be8507..5af8d712 100644 --- a/backend/controller/index.js +++ b/backend/controller/index.js @@ -1,9 +1,14 @@ import express from 'express' const router = express.Router() +const userController = require('./userController') /* GET home page. */ router.get('/', function (req, res, next) { res.json({ success: true }) }) +router.get('/user/sign-in/github', userController.githubLogin) + +router.get('/user/sign-in/github/callback', userController.githubCallback) + module.exports = router diff --git a/backend/controller/userController.js b/backend/controller/userController.js new file mode 100644 index 00000000..dfc1c500 --- /dev/null +++ b/backend/controller/userController.js @@ -0,0 +1,48 @@ +const passport = require('passport') +const jwt = require('jsonwebtoken') + +exports.githubLogin = passport.authenticate('github') + +exports.githubCallback = async (req, res, next) => { + const frontHost = process.env.FRONT_HOST + passport.authenticate('github', (err, profile) => { + if (err || !profile) { + return res.status(200).redirect(frontHost) + } + req.login(profile, { session: false }, err => { + if (err) { + res.send(err) + } + + const token = jwt.sign(profile, process.env.JWT_SECRET) + console.log('token: ', token) + res.cookie('token', token, { + maxAge: 1000 * 60 * 60, + httpOnly: true, + signed: true, + }) + return res.status(200).redirect(frontHost) + }) + })(req, res) +} + +// exports.githubLogin = passport.authenticate('github') + +// exports.githubCallback = async (req, res, next) => { +// const frontHost = process.env.FRONT_HOST +// const profile = req.user.profile +// console.log('githubCallback profile: ', profile) +// if (profile) { +// const token = jwt.sign(profile, process.env.JWT_SECRET) +// console.log('token after') +// res.cookie('token', token, { +// maxAge: 1000 * 60 * 60, +// httpOnly: true, +// signed: true, +// }) +// console.log('res.cookie: ', res.cookie) +// return res.status(200).redirect(frontHost) +// } +// console.log('res.status(200).redirect(frontHost)') +// return res.status(200).redirect(frontHost) +// } From b05f7415b7734214c64a923ce94780f4f8cff6bc Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 01:44:54 +0900 Subject: [PATCH 03/28] Feat: login page add login page add workspace select page for test --- frontend/src/index.js | 5 +++- frontend/src/page/LoginPage.js | 37 ++++++++++++++++++++++++ frontend/src/page/WorkspaceSelectPage.js | 12 ++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 frontend/src/page/LoginPage.js create mode 100644 frontend/src/page/WorkspaceSelectPage.js diff --git a/frontend/src/index.js b/frontend/src/index.js index 6fd1433a..56330c86 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -4,11 +4,14 @@ import './index.css' import App from './page/App' import { BrowserRouter, Route } from 'react-router-dom' import reportWebVitals from './reportWebVitals' +import LoginPage from './page/LoginPage' +import WorkspaceSelectPage from './page/WorkspaceSelectPage' ReactDOM.render( - + + , document.getElementById('root'), diff --git a/frontend/src/page/LoginPage.js b/frontend/src/page/LoginPage.js new file mode 100644 index 00000000..2bcc368c --- /dev/null +++ b/frontend/src/page/LoginPage.js @@ -0,0 +1,37 @@ +import React from 'react' +import styled from 'styled-components' + +const LoginPage = () => { + return ( + <> +

Slack에 로그인

+
+ Login With github +
+ + ) +} + +const LoginButton = styled.button` + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #1da1f2; + outline: none; + background-color: white; + width: 12rem; + height: 2rem; + border-radius: 20px; + & > * { + padding-right: 10px; + } + :hover { + background-color: #fcf7f7; + cursor: pointer; + } + :active { + background-color: #ebebeb; + } +` + +export default LoginPage diff --git a/frontend/src/page/WorkspaceSelectPage.js b/frontend/src/page/WorkspaceSelectPage.js new file mode 100644 index 00000000..4b35a21f --- /dev/null +++ b/frontend/src/page/WorkspaceSelectPage.js @@ -0,0 +1,12 @@ +import React from 'react' + +const WorkspaceSelectPage = () => { + return ( + <> +

내 워크스페이스

+

새 워크스페이스 생성

+ + ) +} + +export default WorkspaceSelectPage From d59643c1bdd9a2b5093b5e40faf0e7a09590b1ed Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 08:42:30 +0900 Subject: [PATCH 04/28] feat: add cors option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cors 오류 해결하기 위해 옵션 추가 --- backend/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/app.js b/backend/app.js index 2278f175..beaa5d9c 100644 --- a/backend/app.js +++ b/backend/app.js @@ -6,6 +6,8 @@ import mongoose from 'mongoose' import controller from './controller' import statusCode from './util/statusCode' import resMessage from './util/resMessage' +import cors from 'cors' + require('dotenv').config() const app = express() @@ -21,6 +23,7 @@ mongoose .catch(err => console.error(err)) app.use(logger('dev')) +app.use(cors({ origin: true, credentials: true })) app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.use(cookieParser()) From d36a1119b7f15d81c8f98b2eb1678dcace226b49 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 09:09:06 +0900 Subject: [PATCH 05/28] Feat: add socket communication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit socket 통신을 위해 socket.io 설치 및 채팅 메시지를 전송(추가)하는 예제 작성 --- backend/app.js | 4 +- backend/chatServer.js | 31 ++++++ backend/package-lock.json | 208 ++++++++++++++++++++++++++++++++++++++ backend/package.json | 4 +- 4 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 backend/chatServer.js diff --git a/backend/app.js b/backend/app.js index beaa5d9c..cda30ae0 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,3 +1,4 @@ +require('dotenv').config() import express from 'express' import path from 'path' import cookieParser from 'cookie-parser' @@ -6,10 +7,9 @@ import mongoose from 'mongoose' import controller from './controller' import statusCode from './util/statusCode' import resMessage from './util/resMessage' +import './chatServer' import cors from 'cors' -require('dotenv').config() - const app = express() mongoose diff --git a/backend/chatServer.js b/backend/chatServer.js new file mode 100644 index 00000000..7aa496b0 --- /dev/null +++ b/backend/chatServer.js @@ -0,0 +1,31 @@ +import { config as dotenv } from 'dotenv' +import express from 'express' +import { createServer } from 'http' +import createChatServer from 'socket.io' +dotenv() + +const server = createServer(express()) +const io = createChatServer(server, { + cors: { origin: process.env.FRONTEND_HOST, credentials: true }, +}) + +const nsp = io.of('chat') +nsp.use((socket, next) => { + // TODO jwt 검증 로직 필요 + next() +}) + +nsp.on('connection', socket => { + socket.on('new message', data => { + // TODO 특정 채널로 전송하도록 변경, db에 저장 필요 (현재는 자신 제외 전체 전송) + socket.broadcast.emit('new message', { + message: data, + }) + }) +}) + +server.listen(process.env.CHAT_PORT, () => { + console.log('chat server created 4000') +}) + +export default server diff --git a/backend/package-lock.json b/backend/package-lock.json index a6353923..91f92655 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1142,12 +1142,92 @@ "defer-to-connect": "^1.0.1" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" + }, + "@types/cors": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.8.tgz", + "integrity": "sha512-fO3gf3DxU2Trcbr75O7obVndW/X5k8rJNZkLXlQWStTHhP71PkRqjwPIEI0yMnJdg9R9OasjU+Bsr+Hr1xy/0w==", + "requires": { + "@types/express": "*" + } + }, + "@types/express": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz", + "integrity": "sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.14.tgz", + "integrity": "sha512-uFTLwu94TfUFMToXNgRZikwPuZdOtDgs3syBtAIr/OXorL1kJqUJT9qCLnRZ5KBOWfZQikQ2xKgR2tnDj1OgDA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" + }, + "@types/node": { + "version": "14.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", + "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==" + }, + "@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", + "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1277,6 +1357,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1610,6 +1695,11 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1716,6 +1806,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1854,6 +1953,45 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.4.tgz", + "integrity": "sha512-4ggUX5pICZU17OTZNFv5+uFE/ZyoK+TIXv2SvxWWX8lwStllQ6Lvvs4lDBqvKpV9EYXNcvlNOcjKChd/mo+8Tw==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.1.0", + "engine.io-parser": "~4.0.0", + "ws": "^7.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "engine.io-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", + "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -3292,6 +3430,11 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -4003,6 +4146,66 @@ "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, + "socket.io": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.3.tgz", + "integrity": "sha512-TC1GnSXhDVmd3bHji5aG7AgWB8UL7E6quACbKra8uFXBqlMwEDbrJFK+tjuIY5Pe9N0L+MAPPDv3pycnn0000A==", + "requires": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": "^14.14.7", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.1.0", + "engine.io": "~4.0.0", + "socket.io-adapter": "~2.0.3", + "socket.io-parser": "~4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz", + "integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ==" + }, + "socket.io-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1.tgz", + "integrity": "sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==", + "requires": { + "component-emitter": "~1.3.0", + "debug": "~4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4571,6 +4774,11 @@ "typedarray-to-buffer": "^3.1.5" } }, + "ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 8c075d77..7644ffd5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,12 +21,14 @@ "concurrently": "^5.3.0", "cookie-parser": "~1.4.4", "core-js": "^3.6.5", + "cors": "^2.8.5", "debug": "~2.6.9", "dotenv": "^8.2.0", "express": "~4.16.1", "mongoose": "^5.10.15", "morgan": "~1.9.1", - "nodemon": "^2.0.4" + "nodemon": "^2.0.4", + "socket.io": "^3.0.3" }, "devDependencies": { "@babel/core": "^7.12.8", From 6b5e5d6cd98e8093ee3e13f8dae4d0a23124e3d3 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 10:11:27 +0900 Subject: [PATCH 06/28] Create PULL_REQUEST_TEMPLATE --- .github/PULL_REQUEST_TEMPLATE | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 00000000..dae0896e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,8 @@ +## Linked Issue +close # + +## 공유할 사항 +- + +## 논의할 사항 +- 없습니다. ❌ From 7cddf56c143b5320b0658c40498a95e6200b9546 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 13:11:25 +0900 Subject: [PATCH 07/28] Feat: add auth api add auth api to backend --- backend/controller/index.js | 2 +- backend/controller/userController.js | 33 +++++++++++----------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/backend/controller/index.js b/backend/controller/index.js index 5af8d712..e2da9be5 100644 --- a/backend/controller/index.js +++ b/backend/controller/index.js @@ -8,7 +8,7 @@ router.get('/', function (req, res, next) { }) router.get('/user/sign-in/github', userController.githubLogin) - router.get('/user/sign-in/github/callback', userController.githubCallback) +router.get('/user/auth', userController.auth) module.exports = router diff --git a/backend/controller/userController.js b/backend/controller/userController.js index dfc1c500..5e0f716f 100644 --- a/backend/controller/userController.js +++ b/backend/controller/userController.js @@ -26,23 +26,16 @@ exports.githubCallback = async (req, res, next) => { })(req, res) } -// exports.githubLogin = passport.authenticate('github') - -// exports.githubCallback = async (req, res, next) => { -// const frontHost = process.env.FRONT_HOST -// const profile = req.user.profile -// console.log('githubCallback profile: ', profile) -// if (profile) { -// const token = jwt.sign(profile, process.env.JWT_SECRET) -// console.log('token after') -// res.cookie('token', token, { -// maxAge: 1000 * 60 * 60, -// httpOnly: true, -// signed: true, -// }) -// console.log('res.cookie: ', res.cookie) -// return res.status(200).redirect(frontHost) -// } -// console.log('res.status(200).redirect(frontHost)') -// return res.status(200).redirect(frontHost) -// } +exports.auth = (req, res) => { + let token = req.signedCookies.token + if (token) { + try { + let decoded = jwt.verify(token, process.env.JWT_SECRET) + return res.json({ verify: true }) + } catch (err) { + return res.json({ verify: false }) + } + } else { + return res.json({ verify: false, message: 'token does not exist' }) + } +} From fea1cc9512cb589fb02549a1d844da513711909e Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 13:12:05 +0900 Subject: [PATCH 08/28] Feat: add auth hook add auth hook to frontend --- frontend/src/hooks/Auth.js | 29 +++++++++++++++++++++++++++++ frontend/src/index.js | 6 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 frontend/src/hooks/Auth.js diff --git a/frontend/src/hooks/Auth.js b/frontend/src/hooks/Auth.js new file mode 100644 index 00000000..bf19f26c --- /dev/null +++ b/frontend/src/hooks/Auth.js @@ -0,0 +1,29 @@ +import React, { useState, useEffect } from 'react' +import axios from 'axios' + +export default function Auth(Component, loginRequired) { + const apiURL = 'http://localhost:5000' + function Authentication(props) { + const [loading, setloading] = useState(true) + useEffect(() => { + axios + .get(apiURL + '/api/user/auth', { withCredentials: true }) + .then(res => { + if (!res.data.verify) { + // 로그인이 되어 있지 않을때 + if (loginRequired) { + props.history.push('/login') + } + } else { + if (!loginRequired) { + // 로그인 유저가 접근하면 안되는 페이지 + props.history.push('/') + } + } + setloading(false) + }) + }, []) + return !loading && + } + return Authentication +} diff --git a/frontend/src/index.js b/frontend/src/index.js index 56330c86..7ef500ce 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,17 +1,17 @@ import React from 'react' import ReactDOM from 'react-dom' import './index.css' -import App from './page/App' import { BrowserRouter, Route } from 'react-router-dom' import reportWebVitals from './reportWebVitals' import LoginPage from './page/LoginPage' import WorkspaceSelectPage from './page/WorkspaceSelectPage' +import Auth from './hooks/Auth' ReactDOM.render( - - + + , document.getElementById('root'), From c9e7e0a0261c31abe81404ad438692279a69f69f Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 13:15:13 +0900 Subject: [PATCH 09/28] Chore: add socket.io-client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 채팅 기능을 위한 socket 라이브러리 추가 --- frontend/package-lock.json | 126 +++++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + 2 files changed, 127 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b475e908..6655d449 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4231,6 +4231,11 @@ "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", "dev": true }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, "@types/eslint": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.5.tgz", @@ -6255,6 +6260,11 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -6316,6 +6326,11 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -7234,6 +7249,11 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -8751,6 +8771,43 @@ "objectorarray": "^1.0.4" } }, + "engine.io-client": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.4.tgz", + "integrity": "sha512-and4JRvjv+BQ4WBLopYUFePxju3ms3aBRk0XjaLdh/t9TKv2LCKtKKWFRoRzIfUZsu3U38FcYqNLuXhfS16vqw==", + "requires": { + "base64-arraybuffer": "0.1.4", + "component-emitter": "~1.3.0", + "debug": "~4.1.0", + "engine.io-parser": "~4.0.1", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.2.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ws": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" + } + } + }, + "engine.io-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", + "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" + }, "enhanced-resolve": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", @@ -10804,6 +10861,11 @@ } } }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -14693,6 +14755,16 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -18476,6 +18548,50 @@ } } }, + "socket.io-client": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.3.tgz", + "integrity": "sha512-kwCJAKb6JMqE9ZYXg78Dgt8rYLSwtJ/g/LJqpb/pOTFRZMSr1cKAsCaisHZ+IBwKHBY7DYOOkjtkHqseY3ZLpw==", + "requires": { + "@types/component-emitter": "^1.2.10", + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "~1.3.0", + "debug": "~4.1.0", + "engine.io-client": "~4.0.0", + "parseuri": "0.0.6", + "socket.io-parser": "~4.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "socket.io-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1.tgz", + "integrity": "sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==", + "requires": { + "component-emitter": "~1.3.0", + "debug": "~4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, "sockjs": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", @@ -21815,6 +21931,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -21891,6 +22012,11 @@ } } }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 8fd6d15f..beb00066 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", "react-scripts": "4.0.0", + "socket.io-client": "^3.0.3", "styled-components": "^5.2.1", "web-vitals": "^0.2.4" }, From 4089678eb9bb65cbab902eaf12fc091c0a37d39f Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 13:27:56 +0900 Subject: [PATCH 10/28] Feat: initialize channel page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit socket 통신 테스트를 위한 채널 페이지 초기화 --- frontend/src/atom/input/Input.js | 14 +++++ frontend/src/atom/input/Input.stories.js | 18 +++++++ frontend/src/index.js | 2 + .../organism/messageEditor/MessageEditor.js | 29 +++++++++++ frontend/src/page/App.js | 51 ++++++++++++++----- frontend/src/page/channel/Channel.js | 43 ++++++++++++++++ 6 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 frontend/src/atom/input/Input.js create mode 100644 frontend/src/atom/input/Input.stories.js create mode 100644 frontend/src/organism/messageEditor/MessageEditor.js create mode 100644 frontend/src/page/channel/Channel.js diff --git a/frontend/src/atom/input/Input.js b/frontend/src/atom/input/Input.js new file mode 100644 index 00000000..3a32719c --- /dev/null +++ b/frontend/src/atom/input/Input.js @@ -0,0 +1,14 @@ +import React from 'react' + +function Input({ placeholder, handleChange, handleKey, value }) { + return ( + + ) +} + +export default Input diff --git a/frontend/src/atom/input/Input.stories.js b/frontend/src/atom/input/Input.stories.js new file mode 100644 index 00000000..ce7871f5 --- /dev/null +++ b/frontend/src/atom/input/Input.stories.js @@ -0,0 +1,18 @@ +import React from 'react' +import Input from './Input' +import { action } from '@storybook/addon-actions' + +export default { + title: 'Example/Input', + component: Input, +} + +const Template = args => + +export const MessageInput = Template.bind({}) +MessageInput.args = { + placeholder: 'Send a message to #example', + handleChange: action(e => { + console.log(e.target.value) + }), +} diff --git a/frontend/src/index.js b/frontend/src/index.js index 6fd1433a..1ff3cd33 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -2,6 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './page/App' +import Channel from './page/channel/Channel' import { BrowserRouter, Route } from 'react-router-dom' import reportWebVitals from './reportWebVitals' @@ -9,6 +10,7 @@ ReactDOM.render( + , document.getElementById('root'), diff --git a/frontend/src/organism/messageEditor/MessageEditor.js b/frontend/src/organism/messageEditor/MessageEditor.js new file mode 100644 index 00000000..ca03a9e4 --- /dev/null +++ b/frontend/src/organism/messageEditor/MessageEditor.js @@ -0,0 +1,29 @@ +import React, { useState } from 'react' + +import Input from '../../atom/input/Input' + +function MessageEditor({ channelTitle, sendMessage }) { + const [message, setMessage] = useState('') + const handleInput = e => { + setMessage(e.target.value) + } + const handleKey = e => { + if (e.key === 'Enter') { + sendMessage(message) + setMessage('') + } + } + return ( +
+ + {/* TODO markdown, chat action 적용 필요 */} +
+ ) +} + +export default MessageEditor diff --git a/frontend/src/page/App.js b/frontend/src/page/App.js index 44ee2c32..e35db9ec 100644 --- a/frontend/src/page/App.js +++ b/frontend/src/page/App.js @@ -1,22 +1,49 @@ -import logo from './logo.svg' import './App.css' +import io from 'socket.io-client' +import { useState, useEffect } from 'react' + +const socket = io('http://localhost:4000/chat', { query: { username: 't1' } }) function App() { + // const [socket, setSocket] = useState(null) + const [msg, setMsg] = useState('') + useEffect(() => { + socket.on('connect', () => { + console.log('connected') + }) + socket.on('disconnect', () => { + console.log('disconnected') + }) + socket.on('new message', data => { + console.log(data) + }) + console.log(socket) + // setSocket(connect('testuser1')) + console.log(socket) + socket.emit('add user', 'username') + return () => { + socket.off('connect') + socket.off('disconnect') + socket.off('new message') + } + // console.log(connect('testuser1')) + }, []) + return ( ) diff --git a/frontend/src/page/channel/Channel.js b/frontend/src/page/channel/Channel.js new file mode 100644 index 00000000..a1ccea42 --- /dev/null +++ b/frontend/src/page/channel/Channel.js @@ -0,0 +1,43 @@ +import React from 'react' +import MessageEditor from '../../organism/messageEditor/MessageEditor' +import io from 'socket.io-client' +import { useState, useEffect } from 'react' + +const socket = io('http://localhost:4000/chat', { + query: { username: 'test1' }, +}) + +function Channel() { + const [messages, setMessages] = useState([]) + // TODO message 전송 template, 추후 구현 + const sendMessage = message => socket.emit('new message', message) + + useEffect(() => { + socket.on('connect', () => { + console.log('connected') + }) + + socket.on('disconnect', () => { + console.log('disconnected') + }) + + socket.on('new message', data => { + setMessages(...messages, data) + }) + + socket.emit('add user', 'username') + return () => { + socket.off('connect') + socket.off('disconnect') + socket.off('new message') + } + }, []) + return ( +
+ {/* TODO messgae component channel header component 추가 필요 */} + +
+ ) +} + +export default Channel From 0bd606626d9e443b75f58b9ff8be995463dda624 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 13:41:34 +0900 Subject: [PATCH 11/28] Feat: initialize channel page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 소켓 통신을 테스트를 위한 초기화 --- frontend/src/atom/input/Input.js | 14 ++++++ frontend/src/atom/input/Input.stories.js | 18 ++++++++ frontend/src/index.js | 2 + .../organism/messageEditor/MessageEditor.js | 29 +++++++++++++ frontend/src/page/channel/Channel.js | 43 +++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 frontend/src/atom/input/Input.js create mode 100644 frontend/src/atom/input/Input.stories.js create mode 100644 frontend/src/organism/messageEditor/MessageEditor.js create mode 100644 frontend/src/page/channel/Channel.js diff --git a/frontend/src/atom/input/Input.js b/frontend/src/atom/input/Input.js new file mode 100644 index 00000000..3a32719c --- /dev/null +++ b/frontend/src/atom/input/Input.js @@ -0,0 +1,14 @@ +import React from 'react' + +function Input({ placeholder, handleChange, handleKey, value }) { + return ( + + ) +} + +export default Input diff --git a/frontend/src/atom/input/Input.stories.js b/frontend/src/atom/input/Input.stories.js new file mode 100644 index 00000000..ce7871f5 --- /dev/null +++ b/frontend/src/atom/input/Input.stories.js @@ -0,0 +1,18 @@ +import React from 'react' +import Input from './Input' +import { action } from '@storybook/addon-actions' + +export default { + title: 'Example/Input', + component: Input, +} + +const Template = args => + +export const MessageInput = Template.bind({}) +MessageInput.args = { + placeholder: 'Send a message to #example', + handleChange: action(e => { + console.log(e.target.value) + }), +} diff --git a/frontend/src/index.js b/frontend/src/index.js index 6fd1433a..1ff3cd33 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -2,6 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './page/App' +import Channel from './page/channel/Channel' import { BrowserRouter, Route } from 'react-router-dom' import reportWebVitals from './reportWebVitals' @@ -9,6 +10,7 @@ ReactDOM.render( + , document.getElementById('root'), diff --git a/frontend/src/organism/messageEditor/MessageEditor.js b/frontend/src/organism/messageEditor/MessageEditor.js new file mode 100644 index 00000000..ca03a9e4 --- /dev/null +++ b/frontend/src/organism/messageEditor/MessageEditor.js @@ -0,0 +1,29 @@ +import React, { useState } from 'react' + +import Input from '../../atom/input/Input' + +function MessageEditor({ channelTitle, sendMessage }) { + const [message, setMessage] = useState('') + const handleInput = e => { + setMessage(e.target.value) + } + const handleKey = e => { + if (e.key === 'Enter') { + sendMessage(message) + setMessage('') + } + } + return ( +
+ + {/* TODO markdown, chat action 적용 필요 */} +
+ ) +} + +export default MessageEditor diff --git a/frontend/src/page/channel/Channel.js b/frontend/src/page/channel/Channel.js new file mode 100644 index 00000000..a1ccea42 --- /dev/null +++ b/frontend/src/page/channel/Channel.js @@ -0,0 +1,43 @@ +import React from 'react' +import MessageEditor from '../../organism/messageEditor/MessageEditor' +import io from 'socket.io-client' +import { useState, useEffect } from 'react' + +const socket = io('http://localhost:4000/chat', { + query: { username: 'test1' }, +}) + +function Channel() { + const [messages, setMessages] = useState([]) + // TODO message 전송 template, 추후 구현 + const sendMessage = message => socket.emit('new message', message) + + useEffect(() => { + socket.on('connect', () => { + console.log('connected') + }) + + socket.on('disconnect', () => { + console.log('disconnected') + }) + + socket.on('new message', data => { + setMessages(...messages, data) + }) + + socket.emit('add user', 'username') + return () => { + socket.off('connect') + socket.off('disconnect') + socket.off('new message') + } + }, []) + return ( +
+ {/* TODO messgae component channel header component 추가 필요 */} + +
+ ) +} + +export default Channel From d84cf4f8ab5460fdc15ab71622f2bba41fe0989b Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 13:50:55 +0900 Subject: [PATCH 12/28] Feat: update root page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 app.js 파일 및 경로 제거 --- frontend/src/index.js | 5 ++-- frontend/src/page/App.css | 38 ------------------------- frontend/src/page/App.js | 52 ----------------------------------- frontend/src/page/App.test.js | 8 ------ frontend/src/page/index.js | 0 frontend/src/page/logo.svg | 7 ----- 6 files changed, 2 insertions(+), 108 deletions(-) delete mode 100644 frontend/src/page/App.css delete mode 100644 frontend/src/page/App.js delete mode 100644 frontend/src/page/App.test.js delete mode 100644 frontend/src/page/index.js delete mode 100644 frontend/src/page/logo.svg diff --git a/frontend/src/index.js b/frontend/src/index.js index 1ff3cd33..4742c73f 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,7 +1,7 @@ import React from 'react' import ReactDOM from 'react-dom' import './index.css' -import App from './page/App' + import Channel from './page/channel/Channel' import { BrowserRouter, Route } from 'react-router-dom' import reportWebVitals from './reportWebVitals' @@ -9,8 +9,7 @@ import reportWebVitals from './reportWebVitals' ReactDOM.render( - - + , document.getElementById('root'), diff --git a/frontend/src/page/App.css b/frontend/src/page/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/frontend/src/page/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.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); - } -} diff --git a/frontend/src/page/App.js b/frontend/src/page/App.js deleted file mode 100644 index e35db9ec..00000000 --- a/frontend/src/page/App.js +++ /dev/null @@ -1,52 +0,0 @@ -import './App.css' -import io from 'socket.io-client' -import { useState, useEffect } from 'react' - -const socket = io('http://localhost:4000/chat', { query: { username: 't1' } }) - -function App() { - // const [socket, setSocket] = useState(null) - const [msg, setMsg] = useState('') - useEffect(() => { - socket.on('connect', () => { - console.log('connected') - }) - socket.on('disconnect', () => { - console.log('disconnected') - }) - socket.on('new message', data => { - console.log(data) - }) - console.log(socket) - // setSocket(connect('testuser1')) - console.log(socket) - socket.emit('add user', 'username') - return () => { - socket.off('connect') - socket.off('disconnect') - socket.off('new message') - } - // console.log(connect('testuser1')) - }, []) - - return ( -
-
- setMsg(e.target.value)} - > - -
-
- ) -} - -export default App diff --git a/frontend/src/page/App.test.js b/frontend/src/page/App.test.js deleted file mode 100644 index 4741580c..00000000 --- a/frontend/src/page/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react' -import App from './App' - -test('renders learn react link', () => { - render() - const linkElement = screen.getByText(/learn react/i) - expect(linkElement).toBeInTheDocument() -}) diff --git a/frontend/src/page/index.js b/frontend/src/page/index.js deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/page/logo.svg b/frontend/src/page/logo.svg deleted file mode 100644 index 6b60c104..00000000 --- a/frontend/src/page/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From ed21a21cd6ef69e336de81ece36274757ef41630 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 14:18:21 +0900 Subject: [PATCH 13/28] Create CODEOWNERS --- .github/CODEOWNERS | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..ae495ad0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @boostcamp-2020/project12-c From ecaeb40254fadc039db6e3685be4069fa88aa2f7 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 14:32:24 +0900 Subject: [PATCH 14/28] Docs: update swagger docs --- backend/docs/swagger.yaml | 454 ++++++++++++++++++++++++++++++-------- 1 file changed, 357 insertions(+), 97 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index be368874..e6a55ce4 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,122 +1,382 @@ ---- openapi: 3.0.0 info: - description: This is a simple API - version: 1.0.0 - title: Simple Inventory API - contact: - email: you@your-company.com + title: Project12-C-Slack-clone API + description: Project12-C-Slack-clone API license: name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html -host: virtserver.swaggerhub.com -basePath: /solo295/test/1.0.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' + version: 1.0.0-oas3 +servers: + - url: 'https://virtserver.swaggerhub.com/solo295/test/1.0.0' tags: - name: admins description: Secured Admin-only calls - name: developers description: Operations available to regular developers -schemes: - - https paths: - /inventory: + /api/workspace: get: - tags: - - developers - summary: searches inventory - description: | - By passing in the appropriate options, you can search for - available inventory in the system - operationId: searchInventory - produces: - - application/json + summary: select user workspace + description: select user workspace userID 미들웨어에서 추가 parameters: - - name: searchString + - name: userID in: query - description: pass an optional search string for looking up inventory - required: false - type: string - - name: skip + description: user id + required: true + explode: true + schema: + type: integer + responses: + '200': + content: + application/json: + schema: + type: object + properties: + workspaces: + type: array + items: + type: object + properties: + workspaceID: + type: integer + workspaceName: + type: string + workspaceURL: + type: string + description: invite url + description: success + '400': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + post: + summary: create new workspace + description: Create new workspace + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + url: + type: string + description: workspace url + description: success response + '400': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + parameters: + - name: userID in: query - description: number of records to skip for pagination - required: false - type: integer - minimum: 0 - format: int32 - - name: limit + description: user id + required: true + style: form + explode: true + schema: + type: string + - name: name + in: query + description: workspace name + required: true + style: form + explode: true + schema: + type: string + - name: channelName in: query - description: maximum number of records to return + description: Channel name created by default required: false - type: integer - maximum: 50 - minimum: 0 - format: int32 + style: form + explode: true + schema: + type: string + /api/workspace/invite: + summary: invite to workspace + description: invite to workspace + post: + summary: invite to workspace + description: invite to workspace + operationId: '' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + url: + type: string + description: invite url + description: success response + '400': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + parameters: + - name: workspaceID + in: query + description: workspace id + required: true + style: form + explode: true + schema: + type: integer + - name: userID + in: query + description: user id + required: true + style: form + explode: true + schema: + type: string + + /api/thread/follow: + summary: update follow config + description: follow 설정을 변경한다. + patch: + summary: update follow config + description: follow 설정을 변경한다. + parameters: + - name: workspaceUserInfoId + in: query + description: workspaceUserInfoId + required: true + explode: true + schema: + type: integer + - name: chatId + in: query + description: chatId + required: true + explode: true + schema: + type: integer responses: '200': - description: search results matching criteria + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: success response + '400': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + /api/channel/description: + summary: update channel description + description: channel description 변경 + patch: + summary: update channel description + description: channel description 변경 + parameters: + - name: channelId + in: query + description: channelId + required: true + explode: true + schema: + type: integer + - name: description + in: query + description: 변경 될 description + required: true + explode: true schema: - type: array - items: - $ref: '#/definitions/InventoryItem' + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: success response + '400': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + /api/channel/topic: + summary: update channel topic + description: channel topic 변경 + patch: + summary: update channel topic + description: channel topic 변경 + parameters: + - name: channelId + in: query + description: channelId + required: true + explode: true + schema: + type: integer + - name: topic + in: query + description: 변경 될 topic + required: true + explode: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: success '400': - description: bad input parameter + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response + /api/channel: + summary: create channel + description: channel 생성 post: - tags: - - admins - summary: adds an inventory item - description: Adds an item to the system - operationId: addInventory - consumes: - - application/json - produces: - - application/json + summary: create channel + description: channel 생성 parameters: - - in: body - name: inventoryItem - description: Inventory item to add - required: false + - name: title + in: query + description: channel title + required: true + explode: true + schema: + type: string + - name: userId + in: query + description: channel creator, middleware로 전달 + required: true + explode: true + schema: + type: string + - name: description + in: query + description: channel description + explode: true + schema: + type: string + - name: channelType + in: query + description: channel type, (private = 0, public = 1, DM = 2) + required: true + explode: true schema: - $ref: '#/definitions/InventoryItem' + type: integer responses: - '201': - description: item created + '200': + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + channelId: + type: integer + description: channelId + description: success response + '400': - description: invalid input, object invalid - '409': - description: an existing item already exists -definitions: - InventoryItem: - type: object - required: - - id - - manufacturer - - name - - releaseDate - properties: - id: - type: string - format: uuid - example: d290f1ee-6c54-4b01-90e6-d701748f0851 - name: - type: string - example: Widget Adapter - releaseDate: - type: string - format: date-time - example: 2016-08-29T09:12:33.001Z - manufacturer: - $ref: '#/definitions/Manufacturer' - Manufacturer: - required: - - name - properties: - name: - type: string - example: ACME Corporation - homePage: - type: string - format: url - example: https://www.acme-corp.com - phone: - type: string - example: 408-867-5309 + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: bad request + default: + description: Default error sample response +components: + schemas: + InventoryItem: + required: + - id + - manufacturer + - name + - releaseDate + type: object + properties: + id: + type: string + format: uuid + example: d290f1ee-6c54-4b01-90e6-d701748f0851 + name: + type: string + example: Widget Adapter + releaseDate: + type: string + format: date-time + example: '2016-08-29T09:12:33.001Z' + manufacturer: + $ref: '#/components/schemas/Manufacturer' + Manufacturer: + required: + - name + properties: + name: + type: string + example: ACME Corporation + homePage: + type: string + format: url + example: 'https://www.acme-corp.com' + phone: + type: string + example: 408-867-5309 From 04d851cd86b769626252456708a4d1dfd32887d4 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 14:48:45 +0900 Subject: [PATCH 15/28] Feat: github login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit github oauth를 이용하여 로그인할 때 계정이 없다면 생성한다. 이미 존재한다면 값만 반환한다. --- backend/config/passport.js | 45 ++++++++++------------------ backend/controller/userController.js | 9 +++--- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/backend/config/passport.js b/backend/config/passport.js index 346c9366..4087d08d 100644 --- a/backend/config/passport.js +++ b/backend/config/passport.js @@ -1,5 +1,6 @@ const passport = require('passport') const GitHubStrategy = require('passport-github').Strategy +const { User } = require('../model/User') require('dotenv').config() @@ -10,45 +11,29 @@ const githubStrategyOption = { } async function gitStrategyLogin(profiles) { - //git 정보가 db에 있는지 확인 - // let user = await userDao.getUser(profiles.username) - // //db에 저장이 안 되어있을 경우 새로 db에 저장 - // if (user === null) { - // try { - // const id = profiles.username - // const profile = profiles.photos[0].value - // const password = 'github' - - // // const result = await userDao.insertUser(id, password, profile) - // } catch (e) { - // return { - // success: false, - // } - // } - // } else if (user !== null && user.password !== 'github') { - // console.log('깃헙아님', user.password) - // //깃헙으로 가입하지 않은 아이디로 이미 가입되어져 있는 경우 오류 처리 - // return { - // success: false, - // } - // } + try { + let user = await User.findOne({ OAuthId: profiles.id }) + if (user === null) { + await User.create({ + OAuthId: profiles.id, + fullName: profiles.username, + isDeleted: false, + }) + } + } catch (err) { + return { success: false } + } - // return { - // success: true, - // userId: profiles.username, - // profile: profiles.photos[0].value, - // } return { success: true, - userId: 'rockpell', - profile: 'a', + userId: profiles.id, } } async function githubVerify(accessToken, refreshToken, profile, done) { try { const result = await gitStrategyLogin(profile) - const user = { userId: result.userId, profile: result.profile } + const user = { userId: result.userId } if (result.success) { return done(null, user) diff --git a/backend/controller/userController.js b/backend/controller/userController.js index 5e0f716f..cc50b7a8 100644 --- a/backend/controller/userController.js +++ b/backend/controller/userController.js @@ -5,17 +5,16 @@ exports.githubLogin = passport.authenticate('github') exports.githubCallback = async (req, res, next) => { const frontHost = process.env.FRONT_HOST - passport.authenticate('github', (err, profile) => { - if (err || !profile) { + passport.authenticate('github', (err, userId) => { + if (err || !userId) { return res.status(200).redirect(frontHost) } - req.login(profile, { session: false }, err => { + req.login(userId, { session: false }, err => { if (err) { res.send(err) } - const token = jwt.sign(profile, process.env.JWT_SECRET) - console.log('token: ', token) + const token = jwt.sign(userId, process.env.JWT_SECRET) res.cookie('token', token, { maxAge: 1000 * 60 * 60, httpOnly: true, From dcdacbfb2239d76ed25b3b9a8260a25f203d52fa Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 15:52:11 +0900 Subject: [PATCH 16/28] Feat: jwt auth middleware, add passport-jwt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jwt를 파싱하여 req에 user라는 데이터를 넣어주는 middleware 제작 userController에 있는 auth를 authCheck로 이름 변경 passport-jwt package 추가 --- backend/config/passport.js | 35 ++++++++++++++++++++++++++++ backend/controller/index.js | 3 ++- backend/controller/userController.js | 2 +- backend/middleware/auth.js | 12 ++++++++++ backend/package-lock.json | 9 +++++++ backend/package.json | 3 ++- 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 backend/middleware/auth.js diff --git a/backend/config/passport.js b/backend/config/passport.js index 4087d08d..8f2de52c 100644 --- a/backend/config/passport.js +++ b/backend/config/passport.js @@ -1,5 +1,6 @@ const passport = require('passport') const GitHubStrategy = require('passport-github').Strategy +const JWTStrategy = require('passport-jwt').Strategy const { User } = require('../model/User') require('dotenv').config() @@ -44,6 +45,40 @@ async function githubVerify(accessToken, refreshToken, profile, done) { } } +const cookieExtractor = req => { + if (req.signedCookies) return req.signedCookies.token + if (req.cookies) return req.cookies +} + +const isExist = async userId => { + try { + let user = await User.findOne({ OAuthId: userId }) + return { + success: true, + userId: user.OAuthId, + } + } catch (err) { + return { success: false } + } +} + +const jwtStrategyOption = { + jwtFromRequest: cookieExtractor, + secretOrKey: process.env.JWT_SECRET, +} +async function jwtVerift(payload, done) { + try { + const result = await isExist(payload.userId) + if (!result.success) { + return done(null, false, { message: 'JWT 토큰 인증에 실패했습니다.' }) + } + return done(null, result) + } catch (err) { + return done(null, false, { message: 'JWT verify err 발생' }) + } +} + module.exports = () => { passport.use(new GitHubStrategy(githubStrategyOption, githubVerify)) + passport.use(new JWTStrategy(jwtStrategyOption, jwtVerift)) } diff --git a/backend/controller/index.js b/backend/controller/index.js index e2da9be5..b338adee 100644 --- a/backend/controller/index.js +++ b/backend/controller/index.js @@ -1,6 +1,7 @@ import express from 'express' const router = express.Router() const userController = require('./userController') +const { Auth } = require('../middleware/auth') /* GET home page. */ router.get('/', function (req, res, next) { @@ -9,6 +10,6 @@ router.get('/', function (req, res, next) { router.get('/user/sign-in/github', userController.githubLogin) router.get('/user/sign-in/github/callback', userController.githubCallback) -router.get('/user/auth', userController.auth) +router.get('/user/auth', userController.authCheck) module.exports = router diff --git a/backend/controller/userController.js b/backend/controller/userController.js index cc50b7a8..357d1fa3 100644 --- a/backend/controller/userController.js +++ b/backend/controller/userController.js @@ -25,7 +25,7 @@ exports.githubCallback = async (req, res, next) => { })(req, res) } -exports.auth = (req, res) => { +exports.authCheck = (req, res) => { let token = req.signedCookies.token if (token) { try { diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js new file mode 100644 index 00000000..c1ef94fc --- /dev/null +++ b/backend/middleware/auth.js @@ -0,0 +1,12 @@ +const passport = require('passport') +require('dotenv').config() + +exports.Auth = (req, res, next) => { + passport.authenticate('jwt', { session: false }, (err, user) => { + if (err || !user || !user.success) { + next({ status: 500, message: 'auth error' }) + } + req.user = user + next() + })(req, res, next) +} diff --git a/backend/package-lock.json b/backend/package-lock.json index 542a3c0a..03590406 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -3578,6 +3578,15 @@ "passport-oauth2": "1.x.x" } }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, "passport-oauth2": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.5.0.tgz", diff --git a/backend/package.json b/backend/package.json index 3ef6d20d..69ce5a2c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,7 +30,8 @@ "morgan": "~1.9.1", "nodemon": "^2.0.4", "passport": "^0.4.1", - "passport-github": "^1.1.0" + "passport-github": "^1.1.0", + "passport-jwt": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.12.8", From d9d3e240dc07a240b93d3dc13e5af33f1e506b39 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 17:01:35 +0900 Subject: [PATCH 17/28] Feat: axios util MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit axios에서 사용되는 메소드들을 util로 만들었습니다. --- frontend/src/hooks/Auth.js | 16 +++---- frontend/src/util/request.js | 81 ++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 frontend/src/util/request.js diff --git a/frontend/src/hooks/Auth.js b/frontend/src/hooks/Auth.js index bf19f26c..b9b3dd73 100644 --- a/frontend/src/hooks/Auth.js +++ b/frontend/src/hooks/Auth.js @@ -1,15 +1,14 @@ import React, { useState, useEffect } from 'react' -import axios from 'axios' +import request from '../util/request' export default function Auth(Component, loginRequired) { - const apiURL = 'http://localhost:5000' function Authentication(props) { const [loading, setloading] = useState(true) useEffect(() => { - axios - .get(apiURL + '/api/user/auth', { withCredentials: true }) - .then(res => { - if (!res.data.verify) { + ;(async () => { + try { + const data = await request.GET('/api/user/auth') + if (!data.verify) { // 로그인이 되어 있지 않을때 if (loginRequired) { props.history.push('/login') @@ -21,7 +20,10 @@ export default function Auth(Component, loginRequired) { } } setloading(false) - }) + } catch (err) { + console.error(err) + } + })() }, []) return !loading && } diff --git a/frontend/src/util/request.js b/frontend/src/util/request.js new file mode 100644 index 00000000..cb53d631 --- /dev/null +++ b/frontend/src/util/request.js @@ -0,0 +1,81 @@ +import axios from 'axios' + +const baseURL = /*process.env.API_URL | */ 'http://localhost:5000' + +const options = { + withCredentials: true, + baseURL, +} + +const GET = async (path, params = null) => { + try { + const res = await axios.get(baseURL + path, { params, ...options }) + return res.data + } catch (err) { + console.error(err) + } +} + +const POST = async (path, data, contentType = 'application/json') => { + try { + const response = await axios.post(baseURL + path, data, { + headers: { + 'Content-Type': contentType, + }, + ...options, + }) + return response.data + } catch (err) { + console.error(err) + } +} + +const DELETE = async (path, params = null) => { + try { + const response = await axios.delete(baseURL + path, { + params, + ...options, + }) + return response.data + } catch (err) { + console.error(err) + } +} + +const PATCH = async (path, data, contentType = 'application/json') => { + try { + const response = await axios.patch(baseURL + path, data, { + headers: { + 'Content-Type': contentType, + }, + ...options, + }) + return response.data + } catch (err) { + console.error(err) + } +} + +const PUT = async (path, data, contentType = 'application/json') => { + try { + const response = await axios.put(baseURL + path, data, { + headers: { + 'Content-Type': contentType, + }, + ...options, + }) + return response.data + } catch (err) { + console.error(err) + } +} + +const request = { + GET, + POST, + DELETE, + PATCH, + PUT, +} + +export default request From 99ff33d04fad03cddd9b993325d3ba45c1b3a5f6 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 17:23:00 +0900 Subject: [PATCH 18/28] Fix: token content name change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jwt에 들어가있는 user 정보에 대한 이름을 id로 변경하였습니다. --- backend/config/passport.js | 23 +++++++++++++---------- backend/controller/userController.js | 8 ++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/backend/config/passport.js b/backend/config/passport.js index 8f2de52c..0d1ea37a 100644 --- a/backend/config/passport.js +++ b/backend/config/passport.js @@ -15,26 +15,29 @@ async function gitStrategyLogin(profiles) { try { let user = await User.findOne({ OAuthId: profiles.id }) if (user === null) { - await User.create({ + const data = await User.create({ OAuthId: profiles.id, fullName: profiles.username, isDeleted: false, }) + return { + success: true, + id: data._id, + } + } + return { + success: true, + id: user._id, } } catch (err) { return { success: false } } - - return { - success: true, - userId: profiles.id, - } } async function githubVerify(accessToken, refreshToken, profile, done) { try { const result = await gitStrategyLogin(profile) - const user = { userId: result.userId } + const user = { id: result.id } if (result.success) { return done(null, user) @@ -52,10 +55,10 @@ const cookieExtractor = req => { const isExist = async userId => { try { - let user = await User.findOne({ OAuthId: userId }) + let user = await User.findOne({ _id: userId }) return { success: true, - userId: user.OAuthId, + id: user._id, } } catch (err) { return { success: false } @@ -68,7 +71,7 @@ const jwtStrategyOption = { } async function jwtVerift(payload, done) { try { - const result = await isExist(payload.userId) + const result = await isExist(payload.id) if (!result.success) { return done(null, false, { message: 'JWT 토큰 인증에 실패했습니다.' }) } diff --git a/backend/controller/userController.js b/backend/controller/userController.js index 357d1fa3..292e7951 100644 --- a/backend/controller/userController.js +++ b/backend/controller/userController.js @@ -5,16 +5,16 @@ exports.githubLogin = passport.authenticate('github') exports.githubCallback = async (req, res, next) => { const frontHost = process.env.FRONT_HOST - passport.authenticate('github', (err, userId) => { - if (err || !userId) { + passport.authenticate('github', (err, id) => { + if (err || !id) { return res.status(200).redirect(frontHost) } - req.login(userId, { session: false }, err => { + req.login(id, { session: false }, err => { if (err) { res.send(err) } - const token = jwt.sign(userId, process.env.JWT_SECRET) + const token = jwt.sign(id, process.env.JWT_SECRET) res.cookie('token', token, { maxAge: 1000 * 60 * 60, httpOnly: true, From dfc3c1ed3fd853b8ccf0fd31d62172b7398c9eba Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 18:34:35 +0900 Subject: [PATCH 19/28] Fix: change env name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Front Host를 나타내는 env의 이름을 변경하였습니다. --- backend/controller/{ => user}/userController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename backend/controller/{ => user}/userController.js (95%) diff --git a/backend/controller/userController.js b/backend/controller/user/userController.js similarity index 95% rename from backend/controller/userController.js rename to backend/controller/user/userController.js index 292e7951..11cb2d54 100644 --- a/backend/controller/userController.js +++ b/backend/controller/user/userController.js @@ -4,7 +4,7 @@ const jwt = require('jsonwebtoken') exports.githubLogin = passport.authenticate('github') exports.githubCallback = async (req, res, next) => { - const frontHost = process.env.FRONT_HOST + const frontHost = process.env.FRONTEND_HOST passport.authenticate('github', (err, id) => { if (err || !id) { return res.status(200).redirect(frontHost) From 22b343947a2e8fccc82e04fde9e2a7e0a4fb87b0 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 18:35:28 +0900 Subject: [PATCH 20/28] Refactor: userController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit userController의 디렉토리 구조를 변경하였습니다. --- backend/controller/index.js | 8 +++----- backend/controller/user/index.js | 9 +++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 backend/controller/user/index.js diff --git a/backend/controller/index.js b/backend/controller/index.js index b338adee..1cb3a6ef 100644 --- a/backend/controller/index.js +++ b/backend/controller/index.js @@ -1,15 +1,13 @@ import express from 'express' +import userController from './user' + const router = express.Router() -const userController = require('./userController') -const { Auth } = require('../middleware/auth') /* GET home page. */ router.get('/', function (req, res, next) { res.json({ success: true }) }) -router.get('/user/sign-in/github', userController.githubLogin) -router.get('/user/sign-in/github/callback', userController.githubCallback) -router.get('/user/auth', userController.authCheck) +router.use('/user', userController) module.exports = router diff --git a/backend/controller/user/index.js b/backend/controller/user/index.js new file mode 100644 index 00000000..8fc33cde --- /dev/null +++ b/backend/controller/user/index.js @@ -0,0 +1,9 @@ +import express from 'express' +const router = express.Router() +const controller = require('./userController') + +router.get('/sign-in/github', controller.githubLogin) +router.get('/sign-in/github/callback', controller.githubCallback) +router.get('/auth', controller.authCheck) + +module.exports = router From 3743c843e3aa49b1e0091b0b3c0aa5f58f312f1e Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 18:38:13 +0900 Subject: [PATCH 21/28] Docs: edit create channel api swagger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit db schema와 parameter 통일 --- backend/docs/swagger.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index e6a55ce4..441d1e98 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -298,9 +298,9 @@ paths: explode: true schema: type: string - - name: userId + - name: creator in: query - description: channel creator, middleware로 전달 + description: channel creator, middleware로 userId 전달 required: true explode: true schema: From 9fcd2b68047ccb5a6fbb47edb6efb3bd9fb47243 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 18:38:44 +0900 Subject: [PATCH 22/28] Chore: update channel schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 필수 조건 추가 --- backend/model/Channel.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/model/Channel.js b/backend/model/Channel.js index 9fedafd0..975d447f 100644 --- a/backend/model/Channel.js +++ b/backend/model/Channel.js @@ -5,6 +5,7 @@ const channelSchema = mongoose.Schema( { title: { type: String, + required: true, }, description: { type: String, @@ -15,9 +16,11 @@ const channelSchema = mongoose.Schema( creator: { type: Schema.Types.ObjectId, ref: 'WorkspaceUserInfo', + required: true, }, channelType: { type: Number, + required: true, }, isDeleted: { type: Boolean, From 002c51e679444c3c21886b7088edc304a712d02e Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 18:39:59 +0900 Subject: [PATCH 23/28] Feat: add utility function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 필수 요소 검증 함수, 오류 탐지 함수 추가 --- backend/util/index.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 backend/util/index.js diff --git a/backend/util/index.js b/backend/util/index.js new file mode 100644 index 00000000..56fd3b67 --- /dev/null +++ b/backend/util/index.js @@ -0,0 +1,28 @@ +import resMessage from './resMessage' +import statusCode from './statusCode' + +const asyncWrapper = callback => { + return (req, res, next) => { + callback(req, res, next).catch(next) + } +} + +const verifyRequiredParams = (...params) => { + for (const param of params) + if (!param) + throw { status: statusCode.BAD_REQUEST, message: resMessage.OUT_OF_VALUE } +} + +const dbErrorHandler = async callback => { + try { + return await callback() + } catch (err) { + console.log(err) + throw { + status: statusCode.DB_ERROR, + message: resMessage.DB_ERROR, + } + } +} + +module.exports = { asyncWrapper, verifyRequiredParams, dbErrorHandler } From 7812226a0f0dbf4922595847f00fa30d627d8153 Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 18:40:18 +0900 Subject: [PATCH 24/28] Feat: add create channel api --- backend/controller/channel/channel.js | 12 ++++++++++++ backend/controller/channel/index.js | 8 ++++++++ backend/controller/index.js | 7 +++---- backend/service/channel.js | 16 ++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 backend/controller/channel/channel.js create mode 100644 backend/controller/channel/index.js create mode 100644 backend/service/channel.js diff --git a/backend/controller/channel/channel.js b/backend/controller/channel/channel.js new file mode 100644 index 00000000..37e42fd9 --- /dev/null +++ b/backend/controller/channel/channel.js @@ -0,0 +1,12 @@ +import { asyncWrapper } from '../../util' +import service from '../../service/channel' + +const createChannel = asyncWrapper(async (req, res) => { + const { code, success, data } = await service.createChannel({ + ...req.body, + creator: req.user, + }) + return res.status(code).json({ success, data }) +}) + +module.exports = { createChannel } diff --git a/backend/controller/channel/index.js b/backend/controller/channel/index.js new file mode 100644 index 00000000..2ad08459 --- /dev/null +++ b/backend/controller/channel/index.js @@ -0,0 +1,8 @@ +import express from 'express' +import controller from './channel' + +const router = express.Router() + +router.post('/', controller.createChannel) + +module.exports = router diff --git a/backend/controller/index.js b/backend/controller/index.js index 58be8507..1578f4fc 100644 --- a/backend/controller/index.js +++ b/backend/controller/index.js @@ -1,9 +1,8 @@ import express from 'express' +import channelController from './channel' + const router = express.Router() -/* GET home page. */ -router.get('/', function (req, res, next) { - res.json({ success: true }) -}) +router.use('/channel', channelController) module.exports = router diff --git a/backend/service/channel.js b/backend/service/channel.js new file mode 100644 index 00000000..dc8527ba --- /dev/null +++ b/backend/service/channel.js @@ -0,0 +1,16 @@ +import { Channel } from '../model/Channel' +import statusCode from '../util/statusCode' +import { verifyRequiredParams, dbErrorHandler } from '../util' + +const createChannel = async params => { + verifyRequiredParams(params.creator, params.title, params.channelType) + + const result = await dbErrorHandler(() => Channel.create(params)) + return { + code: statusCode.CREATED, + data: result, + success: true, + } +} + +module.exports = { createChannel } From 22b16dc1aa69f403b23a271f0d1f9038edf23e5b Mon Sep 17 00:00:00 2001 From: jongwon Date: Thu, 26 Nov 2020 18:46:34 +0900 Subject: [PATCH 25/28] Style: remove Abbreviation --- backend/chatServer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/chatServer.js b/backend/chatServer.js index 7aa496b0..da78cfcd 100644 --- a/backend/chatServer.js +++ b/backend/chatServer.js @@ -9,13 +9,13 @@ const io = createChatServer(server, { cors: { origin: process.env.FRONTEND_HOST, credentials: true }, }) -const nsp = io.of('chat') -nsp.use((socket, next) => { +const namespace = io.of('chat') +namespace.use((socket, next) => { // TODO jwt 검증 로직 필요 next() }) -nsp.on('connection', socket => { +namespace.on('connection', socket => { socket.on('new message', data => { // TODO 특정 채널로 전송하도록 변경, db에 저장 필요 (현재는 자신 제외 전체 전송) socket.broadcast.emit('new message', { From 29b4238647dd9c1a0be0774c218b213f2d4eaa96 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 18:47:02 +0900 Subject: [PATCH 26/28] Docs: add sign api, workspace api to swagger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sign 관련 api와 workspace api를 swagger에 추가하였습니다. --- backend/docs/swagger.yaml | 240 +++++++++++++++++++++++--------------- 1 file changed, 145 insertions(+), 95 deletions(-) diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index be368874..60df89cb 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,122 +1,172 @@ ---- openapi: 3.0.0 info: - description: This is a simple API - version: 1.0.0 title: Simple Inventory API + description: This is a simple API contact: email: you@your-company.com license: name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html -host: virtserver.swaggerhub.com -basePath: /solo295/test/1.0.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' + version: 1.0.0-oas3 +servers: + - url: 'https://virtserver.swaggerhub.com/solo295/test/1.0.0' tags: - name: admins description: Secured Admin-only calls - name: developers description: Operations available to regular developers -schemes: - - https paths: - /inventory: - get: - tags: - - developers - summary: searches inventory - description: | - By passing in the appropriate options, you can search for - available inventory in the system - operationId: searchInventory - produces: - - application/json + /api/workspace: + post: + summary: create new workspace + description: Create new workspace + operationId: creaateWorkspace + responses: + '200': + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: workspace url + description: success response + default: + description: Default error sample response parameters: - - name: searchString + - name: userID in: query - description: pass an optional search string for looking up inventory - required: false - type: string - - name: skip + description: user id + required: true + style: form + explode: true + schema: + type: string + - name: name in: query - description: number of records to skip for pagination - required: false - type: integer - minimum: 0 - format: int32 - - name: limit + description: workspace name + required: true + style: form + explode: true + schema: + type: string + - name: channelName in: query - description: maximum number of records to return + description: Channel name created by default required: false - type: integer - maximum: 50 - minimum: 0 - format: int32 + style: form + explode: true + schema: + type: string + get: + summary: select user workspace + description: select user workspace + operationId: '' + parameters: + - name: oauthId + in: query + description: user id + required: true + explode: true + schema: + type: integer responses: '200': - description: search results matching criteria + content: + application/json: + schema: + type: object + properties: + workspaces: + type: array + items: + type: object + properties: + workspaceID: + type: integer + workspaceName: + type: string + workspaceURL: + type: string + description: invite url + description: success response + default: + description: Default error sample response + /api/workspace/invite: + summary: invite to workspace + description: invite to workspace + post: + summary: invite to workspace + description: invite to workspace + operationId: '' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: invite url + description: success response + default: + description: Default error sample response + parameters: + - name: workspaceID + in: query + description: workspace id + required: true + style: form + explode: true + schema: + type: integer + - name: userID + in: query + description: user id + required: true + style: form + explode: true + schema: + type: string + /api/user/sign-in/github: + summary: workspace user login + description: selectworkspace user login + post: + summary: workspace user login + description: workspace user login + operationId: '' + parameters: + - name: oauthId + in: query + description: workspace user id + required: true + explode: true schema: - type: array - items: - $ref: '#/definitions/InventoryItem' + type: integer + responses: + '200': + description: signin success '400': description: bad input parameter - post: - tags: - - admins - summary: adds an inventory item - description: Adds an item to the system - operationId: addInventory - consumes: - - application/json - produces: - - application/json + /api/user/sign-out: + summary: workspace user signout + description: selectworkspace user signout + delete: + summary: workspace user signout + description: workspace user signout + operationId: '' parameters: - - in: body - name: inventoryItem - description: Inventory item to add - required: false + - name: workspaceUserInfoId + in: query + description: workspace user id + required: true + explode: true schema: - $ref: '#/definitions/InventoryItem' + type: integer responses: - '201': - description: item created + '200': + description: search results matching criteria '400': - description: invalid input, object invalid - '409': - description: an existing item already exists -definitions: - InventoryItem: - type: object - required: - - id - - manufacturer - - name - - releaseDate - properties: - id: - type: string - format: uuid - example: d290f1ee-6c54-4b01-90e6-d701748f0851 - name: - type: string - example: Widget Adapter - releaseDate: - type: string - format: date-time - example: 2016-08-29T09:12:33.001Z - manufacturer: - $ref: '#/definitions/Manufacturer' - Manufacturer: - required: - - name - properties: - name: - type: string - example: ACME Corporation - homePage: - type: string - format: url - example: https://www.acme-corp.com - phone: - type: string - example: 408-867-5309 + description: bad input parameter From 76cbb7f6cd5675684da0a36b1cde9042313e588f Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 19:01:24 +0900 Subject: [PATCH 27/28] Fix: jwt expire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jwt 만료시간을 설정하였습니다. --- backend/controller/user/userController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/controller/user/userController.js b/backend/controller/user/userController.js index 11cb2d54..b3e524ec 100644 --- a/backend/controller/user/userController.js +++ b/backend/controller/user/userController.js @@ -14,7 +14,7 @@ exports.githubCallback = async (req, res, next) => { res.send(err) } - const token = jwt.sign(id, process.env.JWT_SECRET) + const token = jwt.sign(id, process.env.JWT_SECRET, { expiresIn: '1H' }) res.cookie('token', token, { maxAge: 1000 * 60 * 60, httpOnly: true, From d7a8ad3d1e55292b076bde3bafdb3e0e3ace3064 Mon Sep 17 00:00:00 2001 From: rockpell Date: Thu, 26 Nov 2020 19:24:30 +0900 Subject: [PATCH 28/28] Style: apply prettier && Typo correction --- backend/app.js | 7 +------ backend/config/passport.js | 4 ++-- backend/middleware/auth.js | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/backend/app.js b/backend/app.js index 9cb02e14..83986549 100644 --- a/backend/app.js +++ b/backend/app.js @@ -30,12 +30,7 @@ app.use(express.urlencoded({ extended: false })) app.use(express.static(path.join(__dirname, '../dist'))) app.use(cookieParser(process.env.COOKIE_SECRET)) app.use(passport.initialize()) -app.use( - cors({ - origin: true, - credentials: true, - }), -) +app.use(cors({ origin: true, credentials: true })) passportConfig() app.use('/api', controller) diff --git a/backend/config/passport.js b/backend/config/passport.js index 0d1ea37a..0e4fd4d9 100644 --- a/backend/config/passport.js +++ b/backend/config/passport.js @@ -69,7 +69,7 @@ const jwtStrategyOption = { jwtFromRequest: cookieExtractor, secretOrKey: process.env.JWT_SECRET, } -async function jwtVerift(payload, done) { +async function jwtVerify(payload, done) { try { const result = await isExist(payload.id) if (!result.success) { @@ -83,5 +83,5 @@ async function jwtVerift(payload, done) { module.exports = () => { passport.use(new GitHubStrategy(githubStrategyOption, githubVerify)) - passport.use(new JWTStrategy(jwtStrategyOption, jwtVerift)) + passport.use(new JWTStrategy(jwtStrategyOption, jwtVerify)) } diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index c1ef94fc..0dbf6edd 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -4,7 +4,7 @@ require('dotenv').config() exports.Auth = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user) => { if (err || !user || !user.success) { - next({ status: 500, message: 'auth error' }) + next({ status: 403, message: 'auth error' }) } req.user = user next()