├── bs ├── fb ├── fs ├── .DS_Store ├── fe ├── .browserslistrc ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── locales │ │ ├── ko.json │ │ └── en.json │ ├── plugins │ │ └── vuetify.js │ ├── views │ │ ├── test │ │ │ ├── lv0.vue │ │ │ ├── lv1.vue │ │ │ ├── lv2.vue │ │ │ └── lv3.vue │ │ ├── e404.vue │ │ ├── block.vue │ │ ├── user.vue │ │ ├── manage │ │ │ ├── users.vue │ │ │ ├── boards.vue │ │ │ ├── sites.vue │ │ │ ├── pages.vue │ │ │ └── user.vue │ │ ├── sign.vue │ │ ├── dashboard │ │ │ └── index.vue │ │ ├── register.vue │ │ └── board │ │ │ └── index.vue │ ├── components │ │ ├── dashboard │ │ │ ├── trendCard.vue │ │ │ ├── smallCard.vue │ │ │ ├── boardCard.vue │ │ │ └── linkCard.vue │ │ ├── user.vue │ │ ├── imgUpload.vue │ │ ├── HelloWorld.vue │ │ └── manage │ │ │ └── boardCard.vue │ ├── i18n.js │ ├── store.js │ ├── main.js │ ├── router.js │ └── App.vue ├── postcss.config.js ├── babel.config.js ├── vue.config.js ├── .gitignore ├── README.md ├── .eslintrc.js └── package.json ├── yarn.lock ├── be ├── public │ └── stylesheets │ │ └── style.css ├── routes │ └── api │ │ ├── notFound.js │ │ ├── lb │ │ └── index.js │ │ ├── manage │ │ ├── index.js │ │ ├── page │ │ │ └── index.js │ │ ├── site │ │ │ └── index.js │ │ ├── user │ │ │ └── index.js │ │ └── board │ │ │ └── index.js │ │ ├── site │ │ └── index.js │ │ ├── register │ │ └── index.js │ │ ├── page │ │ └── index.js │ │ ├── board │ │ └── index.js │ │ ├── user │ │ └── index.js │ │ ├── test │ │ └── index.js │ │ ├── index.js │ │ ├── sign │ │ └── index.js │ │ ├── comment │ │ └── index.js │ │ └── article │ │ └── index.js ├── models │ ├── pages.js │ ├── boards.js │ ├── comments.js │ ├── sites.js │ ├── articles.js │ └── users.js ├── package.json ├── bin │ └── www └── app.js ├── ecosystem.config.js ├── package.json ├── .gitignore └── README.md /bs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd be && npm start 3 | -------------------------------------------------------------------------------- /fb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd fe && yarn serve 3 | -------------------------------------------------------------------------------- /fs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd fe && yarn serve 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkkmemi/nemv3/HEAD/.DS_Store -------------------------------------------------------------------------------- /fe/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 10 4 | -------------------------------------------------------------------------------- /fe/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkkmemi/nemv3/HEAD/fe/public/favicon.ico -------------------------------------------------------------------------------- /fe/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkkmemi/nemv3/HEAD/fe/src/assets/logo.png -------------------------------------------------------------------------------- /fe/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /fe/src/locales/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "안녕 i18n !!", 3 | "dashboard": "현황", 4 | "today": "오늘" 5 | } 6 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /fe/src/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "hello i18n !!", 3 | "dashboard": "Dashboard", 4 | "today": "Today" 5 | } 6 | -------------------------------------------------------------------------------- /be/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /be/routes/api/notFound.js: -------------------------------------------------------------------------------- 1 | const createError = require('http-errors') 2 | 3 | module.exports = (req, res, next) => { 4 | next(createError(404, `${req.path} not found`)) 5 | } 6 | -------------------------------------------------------------------------------- /fe/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | [ 4 | "@vue/app", 5 | { 6 | "useBuiltIns": "entry" 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /fe/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | import 'vuetify/dist/vuetify.min.css' 4 | 5 | Vue.use(Vuetify, { 6 | iconfont: 'md' 7 | }) 8 | -------------------------------------------------------------------------------- /fe/src/views/test/lv0.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /fe/src/views/test/lv1.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /fe/src/views/test/lv2.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /be/routes/api/lb/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | 3 | router.get('/', (req, res, next) => { 4 | res.send({ success: true }) 5 | }) 6 | 7 | module.exports = router 8 | -------------------------------------------------------------------------------- /fe/src/views/e404.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /fe/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginOptions: { 3 | i18n: { 4 | locale: 'en', 5 | fallbackLocale: 'en', 6 | localeDir: 'locales', 7 | enableInSFC: false 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps : [{ 3 | name: 'nemv', 4 | script: './be/bin/www', 5 | max_memory_restart: '1G', 6 | env: { 7 | NODE_ENV: 'production', 8 | PORT: 80 9 | }, 10 | env_dev: { 11 | NODE_ENV: 'development' 12 | } 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /fe/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /fe/README.md: -------------------------------------------------------------------------------- 1 | # fe 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | -------------------------------------------------------------------------------- /fe/src/views/block.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | -------------------------------------------------------------------------------- /be/models/pages.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const cfg = require('../../config') 3 | 4 | mongoose.set('useCreateIndex', true) 5 | const pageSchema = new mongoose.Schema({ 6 | name: { type: String, default: '', index: true }, 7 | title: { type: String, default: '' }, 8 | inCnt: { type: Number, default: 0 }, 9 | lv: { type: Number, default: 0 } 10 | }) 11 | 12 | module.exports = mongoose.model('Page', pageSchema) 13 | -------------------------------------------------------------------------------- /fe/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nemv", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "DEBUG=be:* NODE_ENV=development node ./be/bin/www", 6 | "serve": "cd fe && yarn serve", 7 | "pr": "cd fe && yarn && yarn build && cd ../be && yarn && cd .. && NODE_ENV=production PORT=80 node ./be/bin/www", 8 | "pm2": "cd fe && yarn && yarn build && cd ../be && yarn && cd .. && pm2 start --env pr" 9 | }, 10 | "dependencies": {} 11 | } 12 | -------------------------------------------------------------------------------- /be/models/boards.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const cfg = require('../../config') 3 | 4 | mongoose.set('useCreateIndex', true) 5 | const boardSchema = new mongoose.Schema({ 6 | name: { type: String, default: '', index: true, unique: true }, 7 | title: { type: String, default: '' }, 8 | lv: { type: Number, default: 0 }, 9 | rmk: { type: String, default: '' } 10 | }) 11 | 12 | module.exports = mongoose.model('Board', boardSchema) 13 | -------------------------------------------------------------------------------- /be/routes/api/manage/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var createError = require('http-errors'); 3 | var router = express.Router(); 4 | 5 | router.all('*', function(req, res, next) { 6 | if (req.user.lv) throw createError(403, '권한이 없습니다.') 7 | next() 8 | }) 9 | 10 | router.use('/user', require('./user')) 11 | router.use('/page', require('./page')) 12 | router.use('/site', require('./site')) 13 | router.use('/board', require('./board')) 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /fe/src/components/dashboard/trendCard.vue: -------------------------------------------------------------------------------- 1 | 16 | 21 | -------------------------------------------------------------------------------- /be/routes/api/site/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Site = require('../../../models/sites') 4 | 5 | router.get('/', (req, res, next) => { 6 | // return res.send({ success: true, d: req.user }) 7 | Site.findOne({}) 8 | .then(r => { 9 | res.send({ success: true, d: r, token: req.token }) 10 | }) 11 | .catch(e => { 12 | res.send({ success: false, msg: e.message }) 13 | }) 14 | }) 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /be/models/comments.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | mongoose.set('useCreateIndex', true) 4 | const commentSchema = new mongoose.Schema({ 5 | content: { type: String, default: '' }, 6 | cnt: { 7 | view: { type: Number, default: 0 }, 8 | like: { type: Number, default: 0 } 9 | }, 10 | ip: { type: String, default: '' }, 11 | _user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true, default: null }, 12 | _article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article', index: true } 13 | }) 14 | 15 | module.exports = mongoose.model('Comment', commentSchema) 16 | -------------------------------------------------------------------------------- /fe/src/views/user.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | -------------------------------------------------------------------------------- /be/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "be", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "axios": "^0.18.0", 10 | "connect-history-api-fallback": "^1.5.0", 11 | "cookie-parser": "~1.4.3", 12 | "cors": "^2.8.4", 13 | "debug": "~2.6.9", 14 | "express": "~4.16.0", 15 | "http-errors": "~1.6.2", 16 | "image-data-uri": "^2.0.0", 17 | "jsonwebtoken": "^8.3.0", 18 | "moment": "^2.22.2", 19 | "mongoose": "^5.3.2", 20 | "morgan": "~1.9.0", 21 | "multer": "^1.4.1", 22 | "pug": "2.0.0-beta11", 23 | "request": "^2.88.0", 24 | "sharp": "^0.21.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /be/models/sites.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const cfg = require('../../config') 3 | 4 | mongoose.set('useCreateIndex', true) 5 | const siteSchema = new mongoose.Schema({ 6 | title: { type: String, default: 'NEMV', index: true }, 7 | copyright: { type: String, default: '© 2018 nemv' }, 8 | dark: { type: Boolean, default: false } 9 | }) 10 | 11 | const Site = mongoose.model('Site', siteSchema) 12 | 13 | Site.findOne() 14 | .then(r => { 15 | if (!r) return Site.create({}) 16 | return Promise.resolve(null) 17 | }) 18 | .then(r => { 19 | if (r) console.log(`${r.title} site is created`) 20 | }) 21 | .catch(e => console.error(e.message)) 22 | 23 | module.exports = Site 24 | -------------------------------------------------------------------------------- /fe/src/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | 4 | Vue.use(VueI18n) 5 | 6 | function loadLocaleMessages () { 7 | const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i) 8 | const messages = {} 9 | locales.keys().forEach(key => { 10 | const matched = key.match(/([A-Za-z0-9-_]+)\./i) 11 | if (matched && matched.length > 1) { 12 | const locale = matched[1] 13 | messages[locale] = locales(key) 14 | } 15 | }) 16 | return messages 17 | } 18 | 19 | export default new VueI18n({ 20 | locale: process.env.VUE_APP_I18N_LOCALE || 'en', 21 | fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', 22 | messages: loadLocaleMessages() 23 | }) 24 | -------------------------------------------------------------------------------- /be/models/articles.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | mongoose.set('useCreateIndex', true) 4 | const articleSchema = new mongoose.Schema({ 5 | title: { type: String, default: '', index: true }, 6 | content: { type: String, default: '' }, 7 | cnt: { 8 | view: { type: Number, default: 0 }, 9 | like: { type: Number, default: 0 } 10 | }, 11 | ip: { type: String, default: '' }, 12 | // _comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment', default: null }], 13 | _user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true, default: null }, 14 | _board: { type: mongoose.Schema.Types.ObjectId, ref: 'Board', index: true } 15 | }) 16 | 17 | module.exports = mongoose.model('Article', articleSchema) 18 | -------------------------------------------------------------------------------- /be/routes/api/register/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const crypto = require('crypto') 4 | const User = require('../../../models/users') 5 | 6 | router.post('/', (req, res) => { 7 | const u = req.body 8 | if (!u.id) throw createError(400, '아이디가 없습니다') 9 | if (!u.pwd) throw createError(400, '비밀번호가 없습니다') 10 | if (!u.name) throw createError(400, '이름이 없습니다.') 11 | 12 | User.findOne({ id: u.id }) 13 | .then((r) => { 14 | if (r) throw new Error('이미 등록되어 있는 아이디입니다.') 15 | return User.create(u) 16 | }) 17 | .then((r) => { 18 | const pwd = crypto.scryptSync(r.pwd, r._id.toString(), 64, { N: 1024 }).toString('hex') 19 | return User.updateOne({ _id: r._id }, { $set: { pwd } }) 20 | }) 21 | .then((r) => { 22 | res.send({ success: true }) 23 | }) 24 | .catch((e) => { 25 | res.send({ success: false, msg: e.message }) 26 | }) 27 | }) 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /be/routes/api/page/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Page = require('../../../models/pages') 4 | 5 | router.post('/', function(req, res, next) { 6 | // return res.send({ success: true, d: req.user }) 7 | // throw createError(403, 'hhhh') 8 | const { name } = req.body 9 | Page.findOne({ name }) 10 | .then((r) => { 11 | if (!r) { 12 | if (req.user.lv) throw new Error(`페이지 ${name} 생성이 안되었습니다`) 13 | return Page.create({ name, title: name }) 14 | } 15 | if (r.lv < req.user.lv) throw new Error(`페이지 ${name} 이용 자격이 없습니다.`) 16 | return Page.updateOne({ _id: r._id }, { $inc: { inCnt: 1 } }) 17 | }) 18 | .then(() => { 19 | // return Page.find() 20 | // }) 21 | // .then((rs) => { 22 | // console.log(rs) 23 | res.send({ success: true, d: req.user, token: req.token }) 24 | }) 25 | .catch(e => next(createError(403, e.message))) 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /fe/src/components/user.vue: -------------------------------------------------------------------------------- 1 | 22 | 41 | -------------------------------------------------------------------------------- /fe/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | token: localStorage.getItem('token'), 9 | sb: { 10 | act: false, 11 | msg: '', 12 | color: 'error' 13 | }, 14 | user: { 15 | name: '일반사용자', 16 | id: '없음', 17 | img: 'https://randomuser.me/api/portraits/men/85.jpg' 18 | } 19 | }, 20 | mutations: { 21 | getToken (state, user) { 22 | state.token = localStorage.getItem('token') 23 | state.user = user 24 | }, 25 | delToken (state) { 26 | localStorage.removeItem('token') 27 | state.token = null 28 | state.user = { 29 | name: '일반사용자', 30 | id: '없음', 31 | img: 'https://randomuser.me/api/portraits/men/85.jpg' 32 | } 33 | }, 34 | pop (state, d) { 35 | state.sb.msg = d.msg 36 | state.sb.color = d.color 37 | state.sb.act = false 38 | if (d.act === undefined) state.sb.act = true 39 | } 40 | }, 41 | actions: { 42 | 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /be/routes/api/board/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Board = require('../../../models/boards') 4 | 5 | router.all('*', function(req, res, next) { 6 | // if (req.user.lv) return res.send({ success: false, msg: '권한이 없습니다.' }) 7 | next() 8 | }) 9 | 10 | router.get('/read/:name', (req, res, next) => { 11 | const name = req.params.name 12 | Board.findOne({ name }) 13 | .then(r => { 14 | // console.log(r) 15 | // 권한으로 못보게 하려면.. 16 | // if (r.lv < req.lv) throw new Error(`${name} 게시판을 볼 수 있는 자격이 없습니다.`) 17 | res.send({ success: true, d: r, token: req.token }) 18 | }) 19 | .catch(e => { 20 | res.send({ success: false, msg: e.message }) 21 | }) 22 | }) 23 | 24 | router.get('/list', (req, res, next) => { 25 | Board.find().sort({ lv: -1 }) 26 | .then(rs => { 27 | res.send({ success: true, ds: rs, token: req.token }) 28 | }) 29 | .catch(e => { 30 | res.send({ success: false, msg: e.message }) 31 | }) 32 | }) 33 | 34 | module.exports = router; 35 | -------------------------------------------------------------------------------- /be/routes/api/manage/page/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Page = require('../../../../models/pages') 4 | 5 | router.get('/', function(req, res, next) { 6 | Page.find() 7 | .then(r => { 8 | res.send({ success: true, pages: r, token: req.token }) 9 | }) 10 | .catch(e => { 11 | res.send({ success: false, msg: e.message }) 12 | }) 13 | }) 14 | 15 | router.put('/:_id', (req, res, next) => { 16 | const _id = req.params._id 17 | Page.updateOne({ _id }, { $set: req.body}) 18 | .then(r => { 19 | res.send({ success: true, msg: r, token: req.token }) 20 | }) 21 | .catch(e => { 22 | res.send({ success: false, msg: e.message }) 23 | }) 24 | }) 25 | 26 | router.delete('/:_id', (req, res, next) => { 27 | const _id = req.params._id 28 | Page.deleteOne({ _id }) 29 | .then(r => { 30 | res.send({ success: true, msg: r, token: req.token }) 31 | }) 32 | .catch(e => { 33 | res.send({ success: false, msg: e.message }) 34 | }) 35 | }) 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /be/routes/api/manage/site/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Site = require('../../../../models/sites') 4 | 5 | router.get('/', function(req, res, next) { 6 | Site.find() 7 | .then(r => { 8 | res.send({ success: true, sites: r, token: req.token }) 9 | }) 10 | .catch(e => { 11 | res.send({ success: false, msg: e.message }) 12 | }) 13 | }); 14 | 15 | router.put('/:_id', (req, res, next) => { 16 | const _id = req.params._id 17 | Site.updateOne({ _id }, { $set: req.body}) 18 | .then(r => { 19 | res.send({ success: true, msg: r, token: req.token }) 20 | }) 21 | .catch(e => { 22 | res.send({ success: false, msg: e.message }) 23 | }) 24 | }) 25 | 26 | router.delete('/:_id', (req, res, next) => { 27 | const _id = req.params._id 28 | Site.deleteOne({ _id }) 29 | .then(r => { 30 | res.send({ success: true, msg: r, token: req.token }) 31 | }) 32 | .catch(e => { 33 | res.send({ success: false, msg: e.message }) 34 | }) 35 | }) 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /fe/src/views/manage/users.vue: -------------------------------------------------------------------------------- 1 | 11 | 45 | -------------------------------------------------------------------------------- /be/routes/api/manage/user/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const User = require('../../../../models/users') 4 | 5 | router.get('/', function(req, res, next) { 6 | User.find().select('-pwd') 7 | .then(r => { 8 | res.send({ success: true, users: r, token: req.token }) 9 | }) 10 | .catch(e => { 11 | res.send({ success: false, msg: e.message }) 12 | }) 13 | }); 14 | 15 | router.put('/:_id', (req, res, next) => { 16 | const _id = req.params._id 17 | User.updateOne({ _id }, { $set: req.body}) 18 | .then(r => { 19 | res.send({ success: true, msg: r, token: req.token }) 20 | }) 21 | .catch(e => { 22 | res.send({ success: false, msg: e.message }) 23 | }) 24 | }) 25 | 26 | router.delete('/:_id', (req, res, next) => { 27 | const _id = req.params._id 28 | User.deleteOne({ _id }) 29 | .then(r => { 30 | res.send({ success: true, msg: r, token: req.token }) 31 | }) 32 | .catch(e => { 33 | res.send({ success: false, msg: e.message }) 34 | }) 35 | }) 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /fe/src/components/dashboard/smallCard.vue: -------------------------------------------------------------------------------- 1 | 30 | 35 | -------------------------------------------------------------------------------- /fe/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | fe 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # config 64 | config/ 65 | 66 | # public 67 | public/ 68 | -------------------------------------------------------------------------------- /be/routes/api/user/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const multer = require('multer') 4 | const sharp = require('sharp') 5 | const imageDataURI = require('image-data-uri') 6 | const fs = require('fs') 7 | const User = require('../../../models/users') 8 | 9 | router.post('/', multer({ dest: 'public/' }).single('bin') ,(req, res, next) => { 10 | // console.log(req.body) 11 | // console.log(req.file) 12 | // console.log(req.user) 13 | if (!req.user._id) throw createError(401, 'xxx') 14 | sharp(req.file.path).resize({ 15 | width: 200, 16 | height: 200, 17 | fit: sharp.fit.cover, 18 | position: sharp.strategy.entropy 19 | }).toBuffer() 20 | .then(bf => { 21 | fs.unlinkSync(req.file.path) 22 | const img = imageDataURI.encode(bf, 'png') 23 | return User.findByIdAndUpdate(req.user._id, { $set: { img } }, { new: true }).select('-img') 24 | // res.send(imageDataURI.encode(bf, 'png')) 25 | }) 26 | .then(r => { 27 | res.setHeader('Content-Type', 'text/plain') 28 | res.send(r._id.toString()) 29 | }) 30 | .catch(e => next(e)) 31 | }) 32 | router.delete('/', (req, res, next) => { 33 | res.status(204).send() 34 | }) 35 | 36 | module.exports = router 37 | -------------------------------------------------------------------------------- /be/routes/api/manage/board/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const Board = require('../../../../models/boards') 4 | 5 | router.post('/', (req, res, next) => { 6 | Board.create(req.body) 7 | .then(r => { 8 | res.send({ success: true, msg: r, token: req.token }) 9 | }) 10 | .catch(e => { 11 | res.send({ success: false, msg: e.message }) 12 | }) 13 | }) 14 | 15 | router.get('/', function(req, res, next) { 16 | Board.find() 17 | .then(rs => { 18 | res.send({ success: true, ds: rs, token: req.token }) 19 | }) 20 | .catch(e => { 21 | res.send({ success: false }) 22 | }) 23 | }); 24 | 25 | router.put('/:_id', (req, res, next) => { 26 | const _id = req.params._id 27 | Board.updateOne({ _id }, { $set: req.body}) 28 | .then(r => { 29 | res.send({ success: true, msg: r, token: req.token }) 30 | }) 31 | .catch(e => { 32 | res.send({ success: false, msg: e.message }) 33 | }) 34 | }) 35 | 36 | router.delete('/:_id', (req, res, next) => { 37 | const _id = req.params._id 38 | Board.deleteOne({ _id }) 39 | .then(r => { 40 | res.send({ success: true, msg: r, token: req.token }) 41 | }) 42 | .catch(e => { 43 | res.send({ success: false, msg: e.message }) 44 | }) 45 | }) 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /fe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'" 10 | }, 11 | "dependencies": { 12 | "@babel/polyfill": "^7.0.0-rc.1", 13 | "@toast-ui/vue-editor": "^1.0.2", 14 | "axios": "^0.18.0", 15 | "filepond": "^3.5.0", 16 | "filepond-plugin-file-validate-type": "^1.2.0", 17 | "filepond-plugin-image-preview": "^3.1.4", 18 | "moment": "^2.22.2", 19 | "vee-validate": "^2.1.2", 20 | "vue": "^2.5.17", 21 | "vue-analytics": "^5.16.0", 22 | "vue-filepond": "^4.0.2", 23 | "vue-i18n": "^8.0.0", 24 | "vue-plugin-load-script": "^1.2.0", 25 | "vue-recaptcha": "^1.1.1", 26 | "vue-router": "^3.0.1", 27 | "vuetify": "^1.3.0", 28 | "vuetify-dialog": "^0.3.4", 29 | "vuetrend": "^0.3.2", 30 | "vuex": "^3.0.1" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "^3.0.4", 34 | "@vue/cli-plugin-eslint": "^3.0.4", 35 | "@vue/cli-service": "^3.0.4", 36 | "@vue/eslint-config-standard": "^3.0.4", 37 | "vue-cli-plugin-i18n": "^0.6.0", 38 | "vue-cli-plugin-vuetify": "^0.2.1", 39 | "vue-template-compiler": "^2.5.17" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /be/routes/api/test/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var createError = require('http-errors'); 3 | var router = express.Router(); 4 | const User = require('../../../models/users') 5 | const Page = require('../../../models/pages') 6 | 7 | router.get('/', function(req, res, next) { 8 | res.send({ msg: 'hello', a: '괜찮아' }) 9 | }); 10 | 11 | router.get('/hello', function(req, res, next) { 12 | res.send({ msg: 'hello', a: 2222 }) 13 | }); 14 | 15 | router.delete('/delAll', function(req, res, next) { 16 | User.deleteMany({}) 17 | .then(r => { 18 | console.log(r) 19 | return Page.deleteMany({}) 20 | }) 21 | .then(r => { 22 | console.log(r) 23 | res.send({ success: true, msg: '싹다지움..', token: req.token }) 24 | }) 25 | .catch(e => { 26 | console.log(e.message) 27 | res.send({ success: false, msg: e.message}) 28 | }) 29 | }); 30 | 31 | router.put('/pageAuth', function(req, res, next) { 32 | Page.updateOne({ 33 | 34 | }) 35 | User.deleteMany({}) 36 | .then(r => { 37 | console.log(r) 38 | return Page.deleteMany({}) 39 | }) 40 | .then(r => { 41 | console.log(r) 42 | res.send({ success: true, msg: '싹다지움..', token: req.token }) 43 | }) 44 | .catch(e => { 45 | console.log(e.message) 46 | res.send({ success: false, msg: e.message}) 47 | }) 48 | }); 49 | 50 | router.all('*', function(req, res, next) { 51 | next(createError(404, '그런 api 없어')); 52 | }); 53 | 54 | module.exports = router; 55 | -------------------------------------------------------------------------------- /fe/src/views/test/lv3.vue: -------------------------------------------------------------------------------- 1 | 34 | 54 | -------------------------------------------------------------------------------- /be/models/users.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const crypto = require('crypto') 3 | const cfg = require('../../config') 4 | 5 | mongoose.set('useCreateIndex', true) 6 | mongoose.set('useFindAndModify', false) 7 | const userSchema = new mongoose.Schema({ 8 | name: { type: String, default: '' }, 9 | age: { type: Number, default: 1 }, 10 | id: { type: String, default: '', unique: true, index: true }, 11 | pwd: { type: String, default: '' }, 12 | lv: { type: Number, default: 2 }, //add 13 | inCnt: { type: Number, default: 0 }, //add 14 | retry: { type: Number, default: 0 }, 15 | img: { type: String, default: '' } 16 | }) 17 | 18 | const User = mongoose.model('User', userSchema) 19 | // User.collection.dropIndexes({ name: 1}) 20 | 21 | User.findOne({ id: cfg.admin.id }) 22 | .then((r) => { 23 | // console.log(r) 24 | if (!r) return User.create({ id: cfg.admin.id, pwd: cfg.admin.pwd, name: cfg.admin.name, lv: 0 }) 25 | // if (r.lv === undefined) return User.updateOne({ _id: r._id }, { $set: { lv: 0, inCnt: 0 } }) // 임시.. 관리자 계정 레벨 0으로.. 26 | return Promise.resolve(r) 27 | }) 28 | .then((r) => { 29 | if (r.pwd !== cfg.admin.pwd) return Promise.resolve(null) 30 | console.log(`admin:${r.id} created!`) 31 | const pwd = crypto.scryptSync(r.pwd, r._id.toString(), 64, { N: 1024 }).toString('hex') 32 | return User.updateOne({ _id: r._id }, { $set: { pwd } }) 33 | }) 34 | .then(r => { 35 | if (r) console.log('pwd changed!') 36 | }) 37 | .catch((e) => { 38 | console.error(e.message) 39 | }) 40 | 41 | module.exports = User 42 | -------------------------------------------------------------------------------- /fe/src/main.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import Vue from 'vue' 3 | import VeeValidate from 'vee-validate' 4 | import LoadScript from 'vue-plugin-load-script' 5 | import VueRecaptcha from 'vue-recaptcha' 6 | import VueAnalytics from 'vue-analytics' 7 | import Trend from 'vuetrend' 8 | import VuetifyDialog from 'vuetify-dialog' 9 | import 'tui-editor/dist/tui-editor.css' 10 | import 'tui-editor/dist/tui-editor-contents.css' 11 | import 'codemirror/lib/codemirror.css' 12 | import './plugins/vuetify' 13 | import App from './App.vue' 14 | import router from './router' 15 | import store from './store' 16 | import cfg from '../config' 17 | import { Editor, Viewer } from '@toast-ui/vue-editor' 18 | import i18n from './i18n' 19 | 20 | Vue.config.productionTip = false 21 | 22 | Vue.prototype.$cfg = cfg 23 | if (process.env.NODE_ENV === 'production' && location.protocol === 'http:' && cfg.httpsOnly) location.replace(`https://${location.hostname}`) 24 | Vue.use(VeeValidate) 25 | Vue.use(LoadScript) 26 | Vue.use(VueAnalytics, { 27 | id: cfg.analyticsID, 28 | router, 29 | autoTracking: { 30 | pageviewOnLoad: false 31 | } 32 | }) 33 | Vue.use(Trend) 34 | Vue.use(VuetifyDialog) 35 | 36 | Vue.loadScript('https://www.google.com/recaptcha/api.js?onload=vueRecaptchaApiLoaded&render=explicit') 37 | .then(() => { 38 | Vue.component('vue-recaptcha', VueRecaptcha) 39 | }) 40 | .catch((e) => { 41 | console.error(`google api load failed: ${e.message}`) 42 | }) 43 | Vue.component('editor', Editor) 44 | Vue.component('viewer', Viewer) 45 | 46 | new Vue({ 47 | router, 48 | store, 49 | i18n, 50 | render: h => h(App) 51 | }).$mount('#app') 52 | -------------------------------------------------------------------------------- /fe/src/components/imgUpload.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 66 | -------------------------------------------------------------------------------- /fe/src/views/sign.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 57 | -------------------------------------------------------------------------------- /fe/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /fe/src/components/dashboard/boardCard.vue: -------------------------------------------------------------------------------- 1 | 44 | 72 | -------------------------------------------------------------------------------- /be/routes/api/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const jwt = require('jsonwebtoken') 4 | const moment = require('moment') 5 | const cfg = require('../../../config') 6 | 7 | router.use('/lb', require('./lb')) 8 | router.use('/sign', require('./sign')) 9 | router.use('/site', require('./site')) 10 | router.use('/board', require('./board')) 11 | 12 | const verifyToken = (t) => { 13 | return new Promise((resolve, reject) => { 14 | if (!t) resolve({ id: 'guest', name: '손님', lv: 3 }) 15 | if ((typeof t) !== 'string') reject(new Error('문자가 아닌 토큰 입니다.')) 16 | if (t.length < 10) resolve({ id: 'guest', name: '손님', lv: 3 }) 17 | jwt.verify(t, cfg.jwt.secretKey, (err, v) => { 18 | if (err) reject(err) 19 | resolve(v) 20 | }) 21 | }) 22 | } 23 | 24 | const signToken = (_id, id, lv, name, exp) => { 25 | return new Promise((resolve, reject) => { 26 | const o = { 27 | issuer: cfg.jwt.issuer, 28 | subject: cfg.jwt.subject, 29 | expiresIn: cfg.jwt.expiresIn, // 3분 30 | algorithm: cfg.jwt.algorithm, 31 | expiresIn: exp 32 | } 33 | jwt.sign({ _id, id, lv, name }, cfg.jwt.secretKey, o, (err, token) => { 34 | if (err) reject(err) 35 | resolve(token) 36 | }) 37 | }) 38 | } 39 | 40 | const getToken = async(t) => { 41 | let vt = await verifyToken(t) 42 | if (vt.lv > 2) return { user: vt, token: null } 43 | const diff = moment(vt.exp * 1000).diff(moment(), 'seconds') 44 | // return vt 45 | // console.log(diff) 46 | const expSec = (vt.exp - vt.iat) 47 | if (diff > expSec / cfg.jwt.expiresInDiv) return { user: vt, token: null } 48 | 49 | const nt = await signToken(vt._id, vt.id, vt.lv, vt.name, expSec) 50 | vt = await verifyToken(nt) 51 | return { user: vt, token: nt } 52 | } 53 | router.all('*', function(req, res, next) { 54 | // 토큰 검사 55 | getToken(req.headers.authorization) 56 | .then((v) => { 57 | // console.log(v) 58 | req.user = v.user 59 | req.token = v.token 60 | next() 61 | }) 62 | // .catch(e => res.send({ success: false, msg: e.message })) 63 | .catch(e => next(createError(401, e.message))) 64 | }) 65 | 66 | router.use('/page', require('./page')) 67 | router.use('/article', require('./article')) 68 | router.use('/comment', require('./comment')) 69 | router.use('/manage', require('./manage')) 70 | router.use('/user', require('./user')) 71 | 72 | router.all('*', require('./notFound')) 73 | 74 | module.exports = router 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nemv3 2 | node express mongo vue 3 | 4 | ## 백엔드 config 파일 세팅 방법 5 | 6 | **config/index.js** 7 | ```javascript 8 | module.exports = { 9 | dbUrl: 'mongodb://localhost:27017/nemv', 10 | admin: { 11 | id: 'admin', 12 | pwd: '1234', 13 | name: '관리자' 14 | }, 15 | jwt: { 16 | secretKey: '아주 어려운 토큰 발급용 키', 17 | issuer: 'xxx.com', 18 | subject: 'user-token', 19 | algorithm: 'HS256', 20 | expiresIn: 60 * 3, // 기본 3분 21 | expiresInRemember: 60 * 60 * 24 * 7 // 기억하기 눌렀을 때 7일 22 | expiresInDiv: 3, // 토큰시간 나누는 기준 23 | }, 24 | recaptchaSecret: '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' // google testkey 25 | } 26 | ``` 27 | 28 | - dbUrl: 데이터베이스 주소 29 | - admin: 최초 웹 구동시 생성될 관리자 30 | 31 | 토큰 만들 때 필요한 정보를 추가합니다. 32 | 33 | - jwt.secretKey: secretKey -> jwt.secretKey 34 | - jwt.issuer: 만든 사람, 서버 35 | - jwt.subject: 토큰 이름 36 | - jwt.algorithm: 암호화 10개중 고르면 됨 37 | 38 | | alg Parameter Value | Digital Signature or MAC Algorithm | 39 | | --- | --- | 40 | |HS256 | HMAC using SHA-256 hash algorithm | 41 | |HS384 | HMAC using SHA-384 hash algorithm| 42 | |HS512 | HMAC using SHA-512 hash algorithm| 43 | |RS256 | RSASSA using SHA-256 hash algorithm| 44 | |RS384 | RSASSA using SHA-384 hash algorithm| 45 | |RS512 | RSASSA using SHA-512 hash algorithm| 46 | |ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm| 47 | |ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm| 48 | |ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm| 49 | |none | No digital signature or MAC value included| 50 | 51 | 참고: [https://www.npmjs.com/package/jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) 52 | 53 | 토큰을 만들때 다른 정보를 추가했으니 해독이 더 어려워 지겠죠? 54 | 55 | - jwt.expiresIn: 기억히기 안 누른 토큰 시간(3분) 56 | - jwt.expiresInRemember: 기억하기 누른 토큰 시간(6일) 57 | - jwt.expiresInDiv: 토큰 시간 나눈 시간(1분 혹은 2일), 토큰 재발급 할때 쓰일 시간입니다. 58 | 59 | - recaptchaSecretKey: 구글 로봇 감지 비밀 키입니다. 60 | 참고: [https://developers.google.com/recaptcha/docs/faq](https://developers.google.com/recaptcha/docs/faq) 61 | 62 | ## 프론트엔드 config 파일 세팅 방법 63 | 64 | **fe/config/index.js** 65 | ```javascript 66 | module.exports = { 67 | recaptchaSiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 68 | analyticsID: 'UA-93372301-1', 69 | httpsOnly: false 70 | } 71 | ``` 72 | 73 | - recaptchaSiteKey: 구글 로봇 감지 사이트 키입니다. 74 | - analyticsID: 구글 애널리틱스 아이디입니다. 75 | - httpsOnly: http로 접속할 경우 https로 다시 로드합니다. 76 | 77 | ## ssl 서버 작동 78 | 79 | 3개의 파일 필요 80 | 81 | - config/keys/site.key 82 | - config/keys/cert.crt 83 | - config/keys/ca.crt 84 | 85 | site key는 로컬 머신에서 제작합니다. 86 | 87 | **site.key 제작 방법** 88 | ```bash 89 | $ mkdir config/keys && cd config/keys 90 | $ openssl req -new -newkey rsa:2048 -nodes -keyout site.key 91 | ``` 92 | 93 | cert, ca는 ssl공급사로부터 받습니다. 94 | -------------------------------------------------------------------------------- /be/routes/api/sign/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const jwt = require('jsonwebtoken') 4 | const crypto = require('crypto') 5 | const request = require('request') 6 | const cfg = require('../../../../config') 7 | const User = require('../../../models/users') 8 | 9 | const signToken = (_id, id, lv, name, rmb) => { 10 | return new Promise((resolve, reject) => { 11 | const o = { 12 | issuer: cfg.jwt.issuer, 13 | subject: cfg.jwt.subject, 14 | expiresIn: cfg.jwt.expiresIn, // 3분 15 | algorithm: cfg.jwt.algorithm 16 | } 17 | if (rmb) o.expiresIn = cfg.jwt.expiresInRemember // 6일 18 | jwt.sign({ _id, id, lv, name }, cfg.jwt.secretKey, o, (err, token) => { 19 | if (err) reject(err) 20 | resolve(token) 21 | }) 22 | }) 23 | } 24 | 25 | router.post('/in', (req, res, next) => { 26 | const { id, pwd, remember } = req.body 27 | if (!id) throw createError(400, '아이디가 없습니다') 28 | if (!pwd) throw createError(400, '비밀번호가 없습니다') 29 | if (remember === undefined) throw createError(400, '기억하기가 없습니다.') 30 | 31 | let u = {} 32 | User.findOne({ id }).lean() 33 | .then((r) => { 34 | if (!r) throw new Error('존재하지 않는 아이디입니다.') 35 | const p = crypto.scryptSync(pwd, r._id.toString(), 64, { N: 1024 }).toString('hex') 36 | if (r.pwd !== p) throw new Error('비밀번호가 틀립니다.') 37 | delete r.pwd 38 | u = r 39 | return signToken(r._id, r.id, r.lv, r.name, remember) 40 | }) 41 | .then((r) => { 42 | res.send({ success: true, token: r, user: u }) 43 | }) 44 | .catch((e) => { 45 | res.send({ success: false, msg: e.message }) 46 | // next(createError(401, e.massage)) 47 | }) 48 | }) 49 | 50 | router.post('/up', (req, res, next) => { 51 | const u = req.body 52 | if (!u.id) throw createError(400, '아이디가 없습니다') 53 | if (!u.pwd) throw createError(400, '비밀번호가 없습니다') 54 | if (!u.name) throw createError(400, '이름이 없습니다') 55 | if (!u.response) throw createError(400, '로봇 검증이 없습니다') 56 | 57 | const ro = { 58 | uri: 'https://www.google.com/recaptcha/api/siteverify', 59 | json: true, 60 | form: { 61 | secret: cfg.recaptchaSecretKey, 62 | response: u.response, 63 | remoteip: req.ip 64 | } 65 | } 66 | request.post(ro, (err, response, body) => { 67 | if (err) throw createError(401, '로봇 검증 실패입니다') 68 | 69 | User.findOne({ id: u.id }) 70 | .then((r) => { 71 | if (r) throw new Error('이미 등록되어 있는 아이디입니다') 72 | return User.create(u) 73 | }) 74 | .then((r) => { 75 | const pwd = crypto.scryptSync(r.pwd, r._id.toString(), 64, { N: 1024 }).toString('hex') 76 | return User.updateOne({ _id: r._id }, { $set: { pwd } }) 77 | }) 78 | .then((r) => { 79 | res.send({ success: true }) 80 | }) 81 | .catch((e) => { 82 | res.send({ success: false, msg: e.message }) 83 | }) 84 | }) 85 | }) 86 | 87 | module.exports = router; 88 | -------------------------------------------------------------------------------- /fe/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 82 | 108 | -------------------------------------------------------------------------------- /fe/src/components/dashboard/linkCard.vue: -------------------------------------------------------------------------------- 1 | 27 | 106 | -------------------------------------------------------------------------------- /be/routes/api/comment/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const request = require('request') 4 | const Board = require('../../../models/boards') 5 | const Article = require('../../../models/articles') 6 | const Comment = require('../../../models/comments') 7 | const cfg = require('../../../../config') 8 | 9 | router.post('/:_article', (req, res, next) => { 10 | const _article = req.params._article 11 | if (!_article) throw createError(400, '게시물이 선택되지 않았습니다') 12 | const { content, response } = req.body 13 | 14 | if (!content) throw createError(400, '내용이 없습니다') 15 | if (!response) throw createError(400, '로봇 검증이 없습니다') 16 | 17 | const ro = { 18 | uri: 'https://www.google.com/recaptcha/api/siteverify', 19 | json: true, 20 | form: { 21 | secret: cfg.recaptchaSecretKey, 22 | response, 23 | remoteip: req.ip 24 | } 25 | } 26 | request.post(ro, (err, response, body) => { 27 | if (err) throw createError(401, '로봇 검증 실패입니다') 28 | 29 | Article.findById(_article) 30 | .then(r => { 31 | if (!r) throw createError(400, '잘못된 게시물입니다') 32 | // if (r.lv < req.user.lv) throw createError(403, '권한이 없습니다') 33 | const cmt = { 34 | content, 35 | _article, 36 | ip: '1.1.1.1',//req.ip, 37 | _user: null 38 | } 39 | if (req.user._id) cmt._user = req.user._id 40 | return Comment.create(cmt) 41 | }) 42 | .then(r => { 43 | if (!r) throw new Error('게시물이 생성되지 않았습니다') 44 | res.send({ success: true, d: r, token: req.token }) 45 | }) 46 | .catch(e => { 47 | res.send({ success: false, msg: e.message }) 48 | }) 49 | }) 50 | }) 51 | 52 | 53 | router.put('/:_id', (req, res, next) => { 54 | if (!req.user._id) throw createError(403, '댓글 수정 권한이 없습니다') 55 | const _id = req.params._id 56 | 57 | const { content } = req.body 58 | 59 | if (!content) throw createError(400, '내용이 없습니다') 60 | 61 | Comment.findById(_id) 62 | .then(r => { 63 | if (!r) throw new Error('댓글 존재하지 않습니다') 64 | if (!r._user) throw new Error('손님 댓글 수정이 안됩니다') 65 | if (r._user.toString() !== req.user._id) throw new Error('본인이 작성한 댓글이 아닙니다') 66 | return Comment.findByIdAndUpdate(_id, { $set: { content } }, { new: true }).populate('_user') 67 | }) 68 | .then(r => { 69 | res.send({ success: true, d: r, token: req.token }) 70 | }) 71 | .catch(e => { 72 | res.send({ success: false, msg: e.message }) 73 | }) 74 | }) 75 | 76 | router.delete('/:_id', (req, res, next) => { 77 | if (!req.user._id) throw createError(403, '댓글 삭제 권한이 없습니다') 78 | const _id = req.params._id 79 | 80 | Comment.findById(_id).populate('_user', 'lv') 81 | .then(r => { 82 | if (!r) throw new Error('댓글이 존재하지 않습니다') 83 | if (!r._user) { 84 | if (req.user.lv > 0) throw new Error('손님 댓글은 삭제가 안됩니다') 85 | } 86 | else { 87 | if (r._user._id.toString() !== req.user._id) { 88 | if (r._user.lv < req.user.lv) throw new Error('본인이 작성한 댓글이 아닙니다') 89 | } 90 | } 91 | return Comment.deleteOne({ _id }) 92 | }) 93 | .then(r => { 94 | res.send({ success: true, d: r, token: req.token }) 95 | }) 96 | .catch(e => { 97 | res.send({ success: false, msg: e.message }) 98 | }) 99 | }) 100 | 101 | module.exports = router; 102 | -------------------------------------------------------------------------------- /be/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('be:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | 92 | // ssl added 93 | 94 | const fs = require('fs') 95 | if (fs.existsSync('./config/keys/site.key') && fs.existsSync('./config/keys/cert.crt') && fs.existsSync('./config/keys/ca.crt')) { 96 | const https = require('https') 97 | const httpsPort = process.env.PORT ? 443 : 3001 98 | const o = { 99 | key: fs.readFileSync('./config/keys/site.key'), 100 | cert: fs.readFileSync('./config/keys/cert.crt'), 101 | ca: [fs.readFileSync('./config/keys/ca.crt')] 102 | } 103 | const serverSSL = https.createServer(o, app).listen(httpsPort) 104 | 105 | serverSSL.on('error', (error) => { 106 | if (error.syscall !== 'listen') { 107 | throw error 108 | } 109 | const bind = `port ${httpsPort}` 110 | 111 | // handle specific listen errors with friendly messages 112 | switch (error.code) { 113 | case 'EACCES': 114 | console.error(bind + ' requires elevated privileges') 115 | process.exit(1) 116 | break 117 | case 'EADDRINUSE': 118 | console.error(bind + ' is already in use') 119 | process.exit(1) 120 | break 121 | default: 122 | throw error 123 | } 124 | }) 125 | 126 | serverSSL.on('listening', () => { 127 | { 128 | const addr = serverSSL.address() 129 | const bind = typeof addr === 'string' 130 | ? 'pipe ' + addr 131 | : 'port ' + addr.port 132 | debug('HTTPS Listening on ' + bind) 133 | } 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /fe/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import axios from 'axios' 4 | import store from './store' 5 | 6 | Vue.use(Router) 7 | 8 | Vue.prototype.$axios = axios 9 | const apiRootPath = process.env.NODE_ENV !== 'production' ? 'http://localhost:3000/api/' : '/api/' 10 | Vue.prototype.$apiRootPath = apiRootPath 11 | axios.defaults.baseURL = apiRootPath 12 | // axios.defaults.headers.common['Authorization'] = localStorage.getItem('token') 13 | 14 | // Add a request interceptor 15 | axios.interceptors.request.use(function (config) { 16 | // Do something before request is sent 17 | config.headers.Authorization = localStorage.getItem('token') 18 | return config 19 | }, function (error) { 20 | // Do something with request error 21 | return Promise.reject(error) 22 | }) 23 | 24 | axios.interceptors.response.use(function (response) { 25 | const token = response.data.token 26 | if (token) localStorage.setItem('token', token) 27 | return response 28 | }, function (error) { 29 | // console.log(error.response) 30 | switch (error.response.status) { 31 | case 400: 32 | store.commit('pop', { msg: `잘못된 요청입니다(${error.response.status}:${error.message})`, color: 'error' }) 33 | break 34 | case 401: 35 | store.commit('delToken') 36 | store.commit('pop', { msg: `인증 오류입니다(${error.response.data.msg}:${error.message})`, color: 'error' }) 37 | break 38 | case 403: 39 | store.commit('pop', { msg: `이용 권한이 없습니다(${error.response.data.msg}:${error.message})`, color: 'warning' }) 40 | break 41 | default: 42 | store.commit('pop', { msg: `알수 없는 오류입니다(${error.response.data.msg}:${error.message})`, color: 'error' }) 43 | break 44 | } 45 | return Promise.reject(error) 46 | }) 47 | 48 | const pageCheck = (to, from, next) => { 49 | axios.post('page', { name: to.path }) 50 | .then((r) => { 51 | if (!r.data.success) throw new Error(r.data.msg) 52 | next() 53 | }) 54 | .catch((e) => { 55 | if (!e.response) store.commit('pop', { msg: e.message, color: 'warning' }) 56 | next(false) 57 | }) 58 | } 59 | 60 | export default new Router({ 61 | mode: 'history', 62 | base: process.env.BASE_URL, 63 | routes: [ 64 | { 65 | path: '/', 66 | name: 'dashboard', 67 | component: () => import('./views/dashboard'), 68 | beforeEnter: pageCheck 69 | }, 70 | { 71 | path: '/board/:name', 72 | name: 'board', 73 | component: () => import('./views/board'), 74 | beforeEnter: pageCheck 75 | }, 76 | { 77 | path: '/test/lv3', 78 | name: 'testLv3', 79 | component: () => import('./views/test/lv3'), 80 | beforeEnter: pageCheck 81 | }, 82 | { 83 | path: '/test/lv2', 84 | name: 'testLv2', 85 | component: () => import('./views/test/lv2'), 86 | beforeEnter: pageCheck 87 | }, 88 | { 89 | path: '/test/lv1', 90 | name: 'testLv1', 91 | component: () => import('./views/test/lv1'), 92 | beforeEnter: pageCheck 93 | }, 94 | { 95 | path: '/test/lv0', 96 | name: 'testLv0', 97 | component: () => import('./views/test/lv0'), 98 | beforeEnter: pageCheck 99 | }, 100 | { 101 | path: '/manage/users', 102 | name: 'manageUsers', 103 | component: () => import('./views/manage/user'), 104 | beforeEnter: pageCheck 105 | }, 106 | { 107 | path: '/manage/pages', 108 | name: 'managePages', 109 | component: () => import('./views/manage/pages'), 110 | beforeEnter: pageCheck 111 | }, 112 | { 113 | path: '/manage/sites', 114 | name: 'manageSites', 115 | component: () => import('./views/manage/sites'), 116 | beforeEnter: pageCheck 117 | }, 118 | { 119 | path: '/manage/boards', 120 | name: 'manageBoards', 121 | component: () => import('./views/manage/boards'), 122 | beforeEnter: pageCheck 123 | }, 124 | { 125 | path: '/user', 126 | name: 'user', 127 | component: () => import('./views/user'), 128 | beforeEnter: pageCheck 129 | }, 130 | { 131 | path: '/sign', 132 | name: '로그인', 133 | component: () => import('./views/sign') 134 | }, 135 | { 136 | path: '/register', 137 | name: '회원가입', 138 | component: () => import('./views/register') 139 | }, 140 | { 141 | path: '*', 142 | name: 'e404', 143 | component: () => import('./views/e404') 144 | } 145 | ] 146 | }) 147 | -------------------------------------------------------------------------------- /fe/src/views/manage/boards.vue: -------------------------------------------------------------------------------- 1 | 93 | 158 | -------------------------------------------------------------------------------- /fe/src/views/manage/sites.vue: -------------------------------------------------------------------------------- 1 | 88 | 146 | -------------------------------------------------------------------------------- /fe/src/components/manage/boardCard.vue: -------------------------------------------------------------------------------- 1 | 78 | 149 | -------------------------------------------------------------------------------- /fe/src/views/manage/pages.vue: -------------------------------------------------------------------------------- 1 | 89 | 156 | -------------------------------------------------------------------------------- /fe/src/views/manage/user.vue: -------------------------------------------------------------------------------- 1 | 88 | 157 | -------------------------------------------------------------------------------- /fe/src/views/register.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 177 | -------------------------------------------------------------------------------- /be/routes/api/article/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router() 2 | const createError = require('http-errors') 3 | const request = require('request') 4 | const Board = require('../../../models/boards') 5 | const Article = require('../../../models/articles') 6 | const Comment = require('../../../models/comments') 7 | const cfg = require('../../../../config') 8 | 9 | router.get('/list/:_board', (req, res, next) => { 10 | const _board = req.params._board 11 | let { search, sort, order, skip, limit } = req.query 12 | if (!(sort && order && skip && limit)) throw createError(400, '잘못된 요청입니다') 13 | if (!search) search = '' 14 | order = parseInt(order) 15 | limit = parseInt(limit) 16 | skip = parseInt(skip) 17 | const s = {} 18 | s[sort] = order 19 | 20 | const f = {} 21 | if (_board) f._board = _board 22 | let total = 0 23 | 24 | Article.countDocuments(f) 25 | .where('title').regex(search) 26 | .then(r => { 27 | total = r 28 | return Article.find(f) 29 | .where('title').regex(search) 30 | .sort(s) 31 | .skip(skip) 32 | .limit(limit) 33 | .select('-content') 34 | .populate('_user', '-pwd') 35 | }) 36 | .then(rs => { 37 | res.send({ success: true, t: total, ds: rs, token: req.token }) 38 | }) 39 | .catch(e => { 40 | res.send({ success: false, msg: e.message }) 41 | }) 42 | }) 43 | 44 | router.get('/read/:_id', (req, res, next) => { 45 | const _id = req.params._id 46 | 47 | let atc = {} 48 | 49 | Article.findByIdAndUpdate(_id, { $inc: { 'cnt.view': 1 } }, { new: true }).lean() 50 | .select('content cnt') 51 | .then(r => { 52 | if (!r) throw new Error('잘못된 게시판입니다') 53 | atc = r 54 | atc._comments = [] 55 | return Comment.find({ _article: atc._id }).populate({ path: '_user', select: 'id name'}).sort({ _id: 1 }).limit(5) 56 | }) 57 | .then(rs => { 58 | if (rs) atc._comments = rs 59 | res.send({ success: true, d: atc, token: req.token }) 60 | }) 61 | .catch(e => { 62 | res.send({ success: false, msg: e.message }) 63 | }) 64 | }) 65 | 66 | // Article.deleteMany({}).then(r => console.log(r)) 67 | 68 | router.post('/:_board', (req, res, next) => { 69 | const _board = req.params._board 70 | if (!_board) throw createError(400, '게시판이 선택되지 않았습니다') 71 | const { title, content, response } = req.body 72 | console.log(req.body) 73 | 74 | if (!title) throw createError(400, '제목이 없습니다') 75 | if (!content) throw createError(400, '내용이 없습니다') 76 | if (!response) throw createError(400, '로봇 검증이 없습니다') 77 | 78 | const ro = { 79 | uri: 'https://www.google.com/recaptcha/api/siteverify', 80 | json: true, 81 | form: { 82 | secret: cfg.recaptchaSecretKey, 83 | response, 84 | remoteip: req.ip 85 | } 86 | } 87 | request.post(ro, (err, response, body) => { 88 | if (err) throw createError(401, '로봇 검증 실패입니다') 89 | 90 | Board.findById(_board) 91 | .then(r => { 92 | if (!r) throw createError(400, '잘못된 게시판입니다') 93 | if (r.lv < req.user.lv) throw createError(403, '권한이 없습니다') 94 | const atc = { 95 | title, 96 | content, 97 | _board, 98 | ip: '1.1.1.1',//req.ip, 99 | _user: null 100 | } 101 | if (req.user._id) atc._user = req.user._id 102 | return Article.create(atc) 103 | }) 104 | .then(r => { 105 | if (!r) throw new Error('게시물이 생성되지 않았습니다') 106 | res.send({ success: true, d: r, token: req.token }) 107 | }) 108 | .catch(e => { 109 | res.send({ success: false, msg: e.message }) 110 | }) 111 | }) 112 | }) 113 | 114 | 115 | router.put('/:_id', (req, res, next) => { 116 | if (!req.user._id) throw createError(403, '게시물 수정 권한이 없습니다') 117 | const _id = req.params._id 118 | 119 | const { title, content, response } = req.body 120 | 121 | if (!title) throw createError(400, '제목이 없습니다') 122 | if (!content) throw createError(400, '내용이 없습니다') 123 | if (!response) throw createError(400, '로봇 검증이 없습니다') 124 | 125 | const ro = { 126 | uri: 'https://www.google.com/recaptcha/api/siteverify', 127 | json: true, 128 | form: { 129 | secret: cfg.recaptchaSecretKey, 130 | response, 131 | remoteip: req.ip 132 | } 133 | } 134 | request.post(ro, (err, response, body) => { 135 | if (err) throw createError(401, '로봇 검증 실패입니다') 136 | 137 | Article.findById(_id) 138 | .then(r => { 139 | if (!r) throw new Error('게시물이 존재하지 않습니다') 140 | if (!r._user) throw new Error('손님 게시물은 수정이 안됩니다') 141 | if (r._user.toString() !== req.user._id) throw new Error('본인이 작성한 게시물이 아닙니다') 142 | return Article.findByIdAndUpdate(_id, { $set: { title, content } }, { new: true }) 143 | }) 144 | .then(r => { 145 | res.send({ success: true, d: r, token: req.token }) 146 | }) 147 | .catch(e => { 148 | res.send({ success: false, msg: e.message }) 149 | }) 150 | }) 151 | }) 152 | 153 | router.delete('/:_id', (req, res, next) => { 154 | if (!req.user._id) throw createError(403, '게시물 삭제 권한이 없습니다') 155 | const _id = req.params._id 156 | 157 | Article.findById(_id).populate('_user', 'lv') 158 | .then(r => { 159 | if (!r) throw new Error('게시물이 존재하지 않습니다') 160 | if (!r._user) { 161 | if (req.user.lv > 0) throw new Error('손님 게시물은 삭제가 안됩니다') 162 | } 163 | else { 164 | if (r._user._id.toString() !== req.user._id) { 165 | if (r._user.lv < req.user.lv) throw new Error('본인이 작성한 게시물이 아닙니다') 166 | } 167 | } 168 | return Article.deleteOne({ _id }) 169 | }) 170 | .then(r => { 171 | res.send({ success: true, d: r, token: req.token }) 172 | }) 173 | .catch(e => { 174 | res.send({ success: false, msg: e.message }) 175 | }) 176 | }) 177 | 178 | module.exports = router; 179 | -------------------------------------------------------------------------------- /fe/src/App.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 284 | 289 | -------------------------------------------------------------------------------- /be/app.js: -------------------------------------------------------------------------------- 1 | var createError = require('http-errors'); 2 | var express = require('express'); 3 | var path = require('path'); 4 | var cookieParser = require('cookie-parser'); 5 | var logger = require('morgan'); 6 | const history = require('connect-history-api-fallback') 7 | const cors = require('cors') 8 | 9 | var app = express(); 10 | 11 | app.use(logger('dev')); 12 | app.use(express.json()); 13 | app.use(express.urlencoded({ extended: false })); 14 | app.use(cookieParser()); 15 | // console.log(path.join(__dirname, '../', 'fe', 'dist')) 16 | if (process.env.NODE_ENV !== 'production') app.use(cors()) 17 | app.use('/api', require('./routes/api')) 18 | app.use(history()) 19 | app.use(express.static(path.join(__dirname, '../', 'fe', 'dist'))); 20 | 21 | 22 | // catch 404 and forward to error handler 23 | app.use(function(req, res, next) { 24 | next(createError(404)); 25 | }); 26 | 27 | // error handler 28 | app.use(function(err, req, res, next) { 29 | // set locals, only providing error in development 30 | res.locals.message = err.message; 31 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 32 | 33 | // render the error page 34 | res.status(err.status || 500); 35 | res.send({ msg: err.message }) 36 | console.error(err.message) 37 | }); 38 | 39 | const mongoose = require('mongoose') 40 | 41 | console.log(`${process.env.NODE_ENV} started!`) 42 | 43 | 44 | const cfg = require('../config') 45 | 46 | mongoose.connect(cfg.dbUrl, { useNewUrlParser: true }, (err) => { 47 | if (err) return console.error(err) 48 | console.log('mongoose connected!') 49 | // User.deleteMany() 50 | // .then(r => console.log(r)) 51 | // .catch(e => console.error(e)) 52 | 53 | // User.create({ name: '하하' }) 54 | // .then(r => console.log(r)) 55 | // .catch(e => console.error(e)) 56 | // User.find() 57 | // .then(r => console.log(r)) 58 | // .catch(e => console.error(e)) 59 | 60 | // User.updateOne({ _id: '5bbe038d7588bf14393d08bf' }, { $set: { age: 34 } }) 61 | // .then(r => { 62 | // console.log(r) 63 | // console.log('updated') 64 | // return User.find() 65 | // }) 66 | // .then(r => console.log(r)) 67 | // .catch(e => console.error(e)) 68 | // User.deleteOne({ name: '하하' }) 69 | // .then(r => console.log(r)) 70 | // .catch(e => console.error(e)) 71 | 72 | }) 73 | 74 | 75 | module.exports = app; 76 | 77 | // var jwt = require('jsonwebtoken'); 78 | // const key = '베리베리어려운키' 79 | // var token = jwt.sign({ id: 'memi', email: 'memi@xxx.com' }, key); 80 | // console.log(token) 81 | // 82 | // jwt.verify(token, 'ㄴㄹㅈㄷㄹ', (err, r) => { 83 | // if (err) return console.error(err.message) 84 | // console.log(r) // bar 85 | // console.log(new Date(r.iat * 1000).toLocaleString()) 86 | // }) 87 | 88 | 89 | // console.log(jwt.sign({ id: 'memi' }, 'abcd')) 90 | // console.log('헬로?') 91 | // jwt.sign({ id: 'memi'}, 'key', (err, token) => { 92 | // console.log(token) 93 | // }) 94 | // console.log('헬로?') 95 | 96 | // User.findOne({}, (err, r) => { 97 | // if (err) return console.error(err) 98 | // console.log(r) 99 | // }) 100 | // User.findOne({}) 101 | // .then(r => console.log(r)) 102 | // .catch(err => console.error(err)) 103 | 104 | 105 | // 콜백 106 | // User.findOne({ name: 'aaa'}, (err, u) => { 107 | // if (err) return console.error(err.message) 108 | // if (!u) { 109 | // console.log(u) // null 110 | // User.create({ name: 'aaa', age: 10 }, (err, cu) => { 111 | // if (err) return console.error(err.message) 112 | // console.log(cu) // { name: 'aaa', age: 10, _id: 5bd81974ad66cf3832db0838, __v: 0 } 113 | // jwt.sign({ name: cu.name, age: cu.age}, key, (err, token) => { 114 | // if (err) return console.error(err.message) 115 | // console.log(token) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 116 | // jwt.verify(token, key, (err, v) => { 117 | // if (err) return console.error(err.message) 118 | // console.log(v) // { name: 'aaa', age: 10, iat: 1540888948 } 119 | // }) 120 | // }) 121 | // }) 122 | // } 123 | // else { 124 | // console.log(u) // { name: 'aaa', age: 10, _id: 5bd81974ad66cf3832db0838, __v: 0 } 125 | // const user = u 126 | // User.updateOne({ _id: u._id }, { $inc: { age: 1 }}, (err, ur) => { 127 | // if (err) return console.error(err.message) 128 | // console.log(ur) // { n: 1, nModified: 1, ... } 129 | // jwt.sign({ name: user.name, age: user.age + 1 }, key, (err, token) => { 130 | // if (err) return console.error(err.message) 131 | // console.log(token) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 132 | // jwt.verify(token, key, (err, v) => { 133 | // if (err) return console.error(err.message) 134 | // console.log(v) // { name: 'aaa', age: 11, iat: 1540889007 } 135 | // }) 136 | // }) 137 | // }) 138 | // } 139 | // }) 140 | 141 | // User.deleteMany({}) 142 | // .then(r => console.log(r)) 143 | // .catch(err => console.error(err)) 144 | 145 | // const signToken = (u, k) => { 146 | // return new Promise((resolve, reject) => { 147 | // jwt.sign({ name: u.name, age: u.age }, k, (err, token) => { 148 | // if (err) reject(err) 149 | // resolve(token) 150 | // }) 151 | // }) 152 | // } 153 | // const verifyToken = (t, k) => { 154 | // return new Promise((resolve, reject) => { 155 | // jwt.verify(t, k, (err, v) => { 156 | // if (err) reject(err) 157 | // resolve(v) 158 | // }) 159 | // }) 160 | // } 161 | 162 | // 프라미스 163 | // let user 164 | // User.findOne({ name: 'aaa' }) 165 | // .then((u) => { 166 | // if (!u) return User.create({ name: 'aaa', age: 10 }) 167 | // return Promise.resolve(u) 168 | // }) 169 | // .then((u) => { 170 | // user = u 171 | // return User.updateOne({ _id: u._id }, { $inc: { age: 1 }}) 172 | // }) 173 | // .then((r) => { 174 | // if (!r.nModified) throw new Error('수정된 것이 없네요..') 175 | // user.age++ 176 | // return signToken(user, key) 177 | // }) 178 | // .then((token) => { 179 | // return verifyToken(token, key) 180 | // }) 181 | // .then(v => console.log(v)) 182 | // .catch((err) => { 183 | // console.error(err.message) 184 | // }) 185 | 186 | // 어씽크 테스트 187 | 188 | // const asyncTest = async (i) => { 189 | // if (i > 10) throw new Error('10보다 큰건 싫어요') 190 | // return i + 2 191 | // } 192 | // 193 | // asyncTest(1) 194 | // .then(r => console.log(`2 더해져서 ${r} 입니다.`)) 195 | // .catch(e => console.error(`에러났네요: ${e.message}`)) 196 | // 197 | // asyncTest(11) 198 | // .then(r => console.log(`2 더해져서 ${r} 입니다.`)) 199 | // .catch(e => console.error(`에러났네요: ${e.message}`)) 200 | 201 | // 어씽크 202 | 203 | // const getToken = async (name) => { 204 | // let u = await User.findOne({ name }) // name: name을 축약하고 await로 기다려 줍니다. 205 | // if (!u) u = await User.create({ name , age: 10 }) // 만들어주고 u를 갱신 합니다. 206 | // if (u.age > 12) throw new Error(`${u.age}는 나이가 너무 많습니다.`) 207 | // const ur = await User.updateOne({ _id: u._id }, { $inc: { age: 1 }}) // age를 증가시키고 ur(user result)에 결과값을 담아놓습니다. 208 | // if (!ur.nModified) throw new Error('수정된 것이 없네요..') // 수정된 값이 없다면 에러와 함께 내보냅니다. 209 | // u = await User.findOne({ _id: u._id }) // age가 증가 된 것으로 갱신해줍니다. 210 | // const token = await signToken(u, key) // 받아 두었던 u로 토큰을 만듭니다. 211 | // const v = await verifyToken(token, key) 212 | // return v 213 | // } 214 | // 215 | // getToken('aaa') 216 | // .then(v => console.log(v)) 217 | // .catch(err => console.error(err.message)) 218 | 219 | // const crypto = require('crypto'); 220 | // const bf = Buffer.alloc(64) 221 | // const s = crypto.randomFillSync(bf) 222 | // console.log(s.toString('hex')) 223 | 224 | // const moment = require('moment') 225 | 226 | // moment.locale('ko') 227 | // 228 | // console.log(moment().fromNow()) 229 | // console.log(moment().add(-5, 'hours').fromNow()) 230 | // console.log(moment().add(5, 'hours').fromNow()) 231 | // 232 | // moment.locale('ko') 233 | // console.log(moment().fromNow()) 234 | // console.log(moment().add(-40, 'hours').fromNow()) 235 | // console.log(moment().add(10, 'hours').fromNow()) 236 | 237 | // const ct = moment() // 현재시간 238 | // const bt = moment().add(-1, 'hours') // 한시간전 239 | // 240 | // console.log(ct.diff(bt)) 241 | // console.log(ct.diff(bt,'seconds')) 242 | // 243 | // console.log(bt.diff(ct)) 244 | // console.log(bt.diff(ct,'seconds')) 245 | 246 | 247 | // const User = require('./models/users') 248 | // const Board = require('./models/boards') 249 | // const Article = require('./models/articles') 250 | 251 | // User.findOne() 252 | // .then(r => console.log(r.id, r._id)) // 5be1c7eb0ff40640c81ecc0d 253 | // 254 | // 255 | // Board.findOne() 256 | // .then(r => console.log(r.name, r._id)) // 5be97f5f8fb2da704ad95273 257 | 258 | // Article.create({ title: 'aaa', content: 'kkfjf', _user: '5be1c7eb0ff40640c81ecc0d', _board: '5be97f5f8fb2da704ad95273' }) 259 | // .then(r => console.log(r)) 260 | 261 | 262 | // Article.find({ _board: '5be97f5f8fb2da704ad95273'}) 263 | // .populate('_user', 'name') 264 | // .populate('_board') 265 | // .then(r => console.log(r)) 266 | 267 | // const net = require('net') 268 | // net.createServer((sock) => { 269 | // 270 | // console.log('connected') 271 | // sock.on('data', (data) => { 272 | // console.log(data) 273 | // }) 274 | // sock.on('error', (err) => { 275 | // console.log(err) 276 | // }) 277 | // sock.on('close', () => { 278 | // console.log('close') 279 | // }) 280 | // }).listen(cfgNet.port) 281 | -------------------------------------------------------------------------------- /fe/src/views/board/index.vue: -------------------------------------------------------------------------------- 1 | 219 | 489 | 494 | --------------------------------------------------------------------------------