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
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 #
+
+## 공유할 사항
+-
+
+## 논의할 사항
+- 없습니다. ❌
diff --git a/backend/app.js b/backend/app.js
index 2278f175..ee549fe3 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,7 +7,10 @@ import mongoose from 'mongoose'
import controller from './controller'
import statusCode from './util/statusCode'
import resMessage from './util/resMessage'
-require('dotenv').config()
+import passport from 'passport'
+import passportConfig from './config/passport'
+import './chatServer'
+import cors from 'cors'
const app = express()
@@ -21,10 +25,14 @@ 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())
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/chatServer.js b/backend/chatServer.js
new file mode 100644
index 00000000..da78cfcd
--- /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 namespace = io.of('chat')
+namespace.use((socket, next) => {
+ // TODO jwt 검증 로직 필요
+ next()
+})
+
+namespace.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/config/passport.js b/backend/config/passport.js
new file mode 100644
index 00000000..0e4fd4d9
--- /dev/null
+++ b/backend/config/passport.js
@@ -0,0 +1,87 @@
+const passport = require('passport')
+const GitHubStrategy = require('passport-github').Strategy
+const JWTStrategy = require('passport-jwt').Strategy
+const { User } = require('../model/User')
+
+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) {
+ try {
+ let user = await User.findOne({ OAuthId: profiles.id })
+ if (user === null) {
+ 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 }
+ }
+}
+
+async function githubVerify(accessToken, refreshToken, profile, done) {
+ try {
+ const result = await gitStrategyLogin(profile)
+ const user = { id: result.id }
+
+ if (result.success) {
+ return done(null, user)
+ }
+ return done(null, false, { message: '깃허브 로그인에 실패했습니다.' })
+ } catch (err) {
+ return done(null, false, { message: 'GitHub verify err 발생' })
+ }
+}
+
+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({ _id: userId })
+ return {
+ success: true,
+ id: user._id,
+ }
+ } catch (err) {
+ return { success: false }
+ }
+}
+
+const jwtStrategyOption = {
+ jwtFromRequest: cookieExtractor,
+ secretOrKey: process.env.JWT_SECRET,
+}
+async function jwtVerify(payload, done) {
+ try {
+ const result = await isExist(payload.id)
+ 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, jwtVerify))
+}
diff --git a/backend/controller/channel/channel.js b/backend/controller/channel/channel.js
index 000468cb..712f6f6b 100644
--- a/backend/controller/channel/channel.js
+++ b/backend/controller/channel/channel.js
@@ -1,9 +1,11 @@
+import { asyncWrapper } from '../../util'
+import service from '../../service/channel'
+
const { WorkspaceUserInfo } = require('../../model/WorkspaceUserInfo')
const { Channel } = require('../../model/Channel')
const { ChannelConfig } = require('../../model/ChannelConfig')
const { Chat } = require('../../model/Chat')
-/* GET /api/channle get channel list */
const getChannelList = async (req, res, next) => {
try {
const workspaceUserInfoId = req.query.workspaceUserInfoId
@@ -116,9 +118,20 @@ const muteChannel = async (req, res, next) => {
}
}
+
+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 = {
getChannelList,
getChannelHeaderInfo,
inviteUser,
muteChannel,
+ createChannel
}
+
diff --git a/backend/controller/channel/index.js b/backend/controller/channel/index.js
index a173dc40..a14f0c96 100644
--- a/backend/controller/channel/index.js
+++ b/backend/controller/channel/index.js
@@ -1,17 +1,20 @@
const express = require('express')
const router = express.Router()
-const channelController = require('./channel')
+const controller = require('./channel')
/* GET /api/channle get channel list */
-router.get('/', channelController.getChannelList)
+router.get('/', controller.getChannelList)
+
+
+router.post('/', controller.createChannel)
/* GET /api/channle/{channelId}/info get channel header info */
-router.get('/:channelId/info', channelController.getChannelHeaderInfo)
+router.get('/:channelId/info', controller.getChannelHeaderInfo)
/* POST /api/channle/invite invite user to channel */
-router.post('/invite', channelController.inviteUser)
+router.post('/invite', controller.inviteUser)
/* PATCH /api/channle/mute mute channel */
-router.patch('/mute', channelController.muteChannel)
+router.patch('/mute', controller.muteChannel)
module.exports = router
diff --git a/backend/controller/index.js b/backend/controller/index.js
index 73c74a46..5c6e880e 100644
--- a/backend/controller/index.js
+++ b/backend/controller/index.js
@@ -1,10 +1,13 @@
import express from 'express'
+
import channelCotroller from './channel'
import searchCotroller from './search'
+import userController from './user'
const router = express.Router()
router.use('/channel', channelCotroller)
router.use('/search', searchCotroller)
+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
diff --git a/backend/controller/user/userController.js b/backend/controller/user/userController.js
new file mode 100644
index 00000000..b3e524ec
--- /dev/null
+++ b/backend/controller/user/userController.js
@@ -0,0 +1,40 @@
+const passport = require('passport')
+const jwt = require('jsonwebtoken')
+
+exports.githubLogin = passport.authenticate('github')
+
+exports.githubCallback = async (req, res, next) => {
+ const frontHost = process.env.FRONTEND_HOST
+ passport.authenticate('github', (err, id) => {
+ if (err || !id) {
+ return res.status(200).redirect(frontHost)
+ }
+ req.login(id, { session: false }, err => {
+ if (err) {
+ res.send(err)
+ }
+
+ const token = jwt.sign(id, process.env.JWT_SECRET, { expiresIn: '1H' })
+ res.cookie('token', token, {
+ maxAge: 1000 * 60 * 60,
+ httpOnly: true,
+ signed: true,
+ })
+ return res.status(200).redirect(frontHost)
+ })
+ })(req, res)
+}
+
+exports.authCheck = (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' })
+ }
+}
diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml
index be368874..0912c14c 100644
--- a/backend/docs/swagger.yaml
+++ b/backend/docs/swagger.yaml
@@ -1,122 +1,424 @@
----
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':
- description: search results matching criteria
+ 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: array
- items:
- $ref: '#/definitions/InventoryItem'
+ type: integer
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ success:
+ type: boolean
+ description: success response
'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/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: 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':
+ 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:
- $ref: '#/definitions/InventoryItem'
+ type: string
+ - name: creator
+ in: query
+ description: channel creator, middleware로 userId 전달
+ 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:
+ 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
+ /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: integer
+ responses:
+ '200':
+ description: signin success
+ '400':
+ description: bad input parameter
+ /api/user/sign-out:
+ summary: workspace user signout
+ description: selectworkspace user signout
+ delete:
+ summary: workspace user signout
+ description: workspace user signout
+ operationId: ''
+ parameters:
+ - name: workspaceUserInfoId
+ in: query
+ description: workspace user id
+ required: true
+ explode: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: search results matching criteria
+ '400':
+ description: bad input parameter
+
+
+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
diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js
new file mode 100644
index 00000000..0dbf6edd
--- /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: 403, message: 'auth error' })
+ }
+ req.user = user
+ next()
+ })(req, res, next)
+}
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,
diff --git a/backend/package-lock.json b/backend/package-lock.json
index a6353923..4a0e0752 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,15 @@
"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=="
+ "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",
@@ -1449,6 +1538,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",
@@ -1610,6 +1704,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 +1815,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 +1933,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",
@@ -1854,6 +1970,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",
@@ -2938,6 +3093,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 +3210,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 +3525,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 +3689,49 @@
"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-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",
+ "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 +3776,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",
@@ -4003,6 +4294,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",
@@ -4303,6 +4654,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",
@@ -4571,6 +4927,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..f17b0f58 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -19,14 +19,20 @@
"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",
+ "passport-jwt": "^4.0.0"
+ "socket.io": "^3.0.3"
},
"devDependencies": {
"@babel/core": "^7.12.8",
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 }
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 }
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b475e908..a0565901 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",
@@ -5484,6 +5489,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",
@@ -6255,6 +6268,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 +6334,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 +7257,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 +8779,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 +10869,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 +14763,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 +18556,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 +21939,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 +22020,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..6fa08c4e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -7,10 +7,12 @@
"@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",
"react-scripts": "4.0.0",
+ "socket.io-client": "^3.0.3",
"styled-components": "^5.2.1",
"web-vitals": "^0.2.4"
},
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/hooks/Auth.js b/frontend/src/hooks/Auth.js
new file mode 100644
index 00000000..b9b3dd73
--- /dev/null
+++ b/frontend/src/hooks/Auth.js
@@ -0,0 +1,31 @@
+import React, { useState, useEffect } from 'react'
+import request from '../util/request'
+
+export default function Auth(Component, loginRequired) {
+ function Authentication(props) {
+ const [loading, setloading] = useState(true)
+ useEffect(() => {
+ ;(async () => {
+ try {
+ const data = await request.GET('/api/user/auth')
+ if (!data.verify) {
+ // 로그인이 되어 있지 않을때
+ if (loginRequired) {
+ props.history.push('/login')
+ }
+ } else {
+ if (!loginRequired) {
+ // 로그인 유저가 접근하면 안되는 페이지
+ props.history.push('/')
+ }
+ }
+ setloading(false)
+ } catch (err) {
+ console.error(err)
+ }
+ })()
+ }, [])
+ return !loading &&
- Edit src/App.js
and save to reload.
-