Showing
5 changed files
with
243 additions
and
81 deletions
.env
0 → 100644
1 | +COOKIE_SECRET=auction | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
.gitignore
deleted
100644 → 0
1 | - | ||
2 | -# Created by https://www.gitignore.io/api/node | ||
3 | - | ||
4 | -### Node ### | ||
5 | -# Logs | ||
6 | -logs | ||
7 | -*.log | ||
8 | -npm-debug.log* | ||
9 | -yarn-debug.log* | ||
10 | -yarn-error.log* | ||
11 | - | ||
12 | -# Runtime data | ||
13 | -pids | ||
14 | -*.pid | ||
15 | -*.seed | ||
16 | -*.pid.lock | ||
17 | - | ||
18 | -# Directory for instrumented libs generated by jscoverage/JSCover | ||
19 | -lib-cov | ||
20 | - | ||
21 | -# Coverage directory used by tools like istanbul | ||
22 | -coverage | ||
23 | - | ||
24 | -# nyc test coverage | ||
25 | -.nyc_output | ||
26 | - | ||
27 | -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||
28 | -.grunt | ||
29 | - | ||
30 | -# Bower dependency directory (https://bower.io/) | ||
31 | -bower_components | ||
32 | - | ||
33 | -# node-waf configuration | ||
34 | -.lock-wscript | ||
35 | - | ||
36 | -# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
37 | -build/Release | ||
38 | - | ||
39 | -# Dependency directories | ||
40 | -node_modules/ | ||
41 | -jspm_packages/ | ||
42 | -keys/api_option.js | ||
43 | -keys/db_option.js | ||
44 | -# TypeScript v1 declaration files | ||
45 | -typings/ | ||
46 | - | ||
47 | -# Optional npm cache directory | ||
48 | -.npm | ||
49 | - | ||
50 | -# Optional eslint cache | ||
51 | -.eslintcache | ||
52 | - | ||
53 | -# Optional REPL history | ||
54 | -.node_repl_history | ||
55 | - | ||
56 | -# Output of 'npm pack' | ||
57 | -*.tgz | ||
58 | - | ||
59 | -# Yarn Integrity file | ||
60 | -.yarn-integrity | ||
61 | - | ||
62 | -# dotenv environment variables file | ||
63 | -.env | ||
64 | - | ||
65 | -# parcel-bundler cache (https://parceljs.org/) | ||
66 | -.cache | ||
67 | - | ||
68 | -# next.js build output | ||
69 | -.next | ||
70 | - | ||
71 | -# nuxt.js build output | ||
72 | -.nuxt | ||
73 | - | ||
74 | -# vuepress build output | ||
75 | -.vuepress/dist | ||
76 | - | ||
77 | -# Serverless directories | ||
78 | -.serverless | ||
79 | - | ||
80 | - | ||
81 | -# End of https://www.gitignore.io/api/node | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
routes/auth.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const passport = require('passport'); | ||
3 | +const bcrypt = require('bcrypt'); | ||
4 | +const { isLoggedIn, isNotLoggedIn } = require('./middlewares'); | ||
5 | +const { User } = require('../models'); | ||
6 | + | ||
7 | +const router = express.Router(); | ||
8 | + | ||
9 | +router.post('/join', isNotLoggedIn, async (req, res, next) => { | ||
10 | + const { email, nick, password, money } = req.body; | ||
11 | + try { | ||
12 | + const exUser = await User.find({ where: { email } }); | ||
13 | + if (exUser) { | ||
14 | + req.flash('joinError', '이미 가입된 이메일입니다.'); | ||
15 | + return res.redirect('/join'); | ||
16 | + } | ||
17 | + const hash = await bcrypt.hash(password, 12); | ||
18 | + await User.create({ | ||
19 | + email, | ||
20 | + nick, | ||
21 | + password: hash, | ||
22 | + money, | ||
23 | + }); | ||
24 | + return res.redirect('/'); | ||
25 | + } catch (error) { | ||
26 | + console.error(error); | ||
27 | + return next(error); | ||
28 | + } | ||
29 | +}); | ||
30 | + | ||
31 | +router.post('/login', isNotLoggedIn, (req, res, next) => { | ||
32 | + passport.authenticate('local', (authError, user, info) => { | ||
33 | + if (authError) { | ||
34 | + console.error(authError); | ||
35 | + return next(authError); | ||
36 | + } | ||
37 | + if (!user) { | ||
38 | + req.flash('loginError', info.message); | ||
39 | + return res.redirect('/'); | ||
40 | + } | ||
41 | + return req.login(user, (loginError) => { | ||
42 | + if (loginError) { | ||
43 | + console.error(loginError); | ||
44 | + return next(loginError); | ||
45 | + } | ||
46 | + return res.redirect('/'); | ||
47 | + }); | ||
48 | + })(req, res, next); | ||
49 | +}); | ||
50 | + | ||
51 | +router.get('/logout', isLoggedIn, (req, res) => { | ||
52 | + req.logout(); | ||
53 | + req.session.destroy(); | ||
54 | + res.redirect('/'); | ||
55 | +}); | ||
56 | + | ||
57 | +module.exports = router; |
routes/index.js
0 → 100644
1 | +const express = require('express'); | ||
2 | +const multer = require('multer'); | ||
3 | +const path = require('path'); | ||
4 | +const fs = require('fs'); | ||
5 | +const schedule = require('node-schedule'); | ||
6 | + | ||
7 | +const { Good, Auction, User, sequelize } = require('../models'); | ||
8 | +const { isLoggedIn, isNotLoggedIn } = require('./middlewares'); | ||
9 | + | ||
10 | +const router = express.Router(); | ||
11 | + | ||
12 | +router.use((req, res, next) => { | ||
13 | + res.locals.user = req.user; | ||
14 | + next(); | ||
15 | +}); | ||
16 | + | ||
17 | +router.get('/', async (req, res, next) => { | ||
18 | + try { | ||
19 | + const goods = await Good.findAll({ where: { soldId: null } }); | ||
20 | + res.render('main', { | ||
21 | + title: 'NodeAuction', | ||
22 | + goods, | ||
23 | + loginError: req.flash('loginError'), | ||
24 | + }); | ||
25 | + } catch (error) { | ||
26 | + console.error(error); | ||
27 | + next(error); | ||
28 | + } | ||
29 | +}); | ||
30 | + | ||
31 | +router.get('/join', isNotLoggedIn, (req, res) => { | ||
32 | + res.render('join', { | ||
33 | + title: '회원가입 - NodeAuction', | ||
34 | + joinError: req.flash('joinError'), | ||
35 | + }); | ||
36 | +}); | ||
37 | + | ||
38 | +router.get('/good', isLoggedIn, (req, res) => { | ||
39 | + res.render('good', { title: '상품 등록 - NodeAuction' }); | ||
40 | +}); | ||
41 | + | ||
42 | +fs.readdir('uploads', (error) => { | ||
43 | + if (error) { | ||
44 | + console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.'); | ||
45 | + fs.mkdirSync('uploads'); | ||
46 | + } | ||
47 | +}); | ||
48 | +const upload = multer({ | ||
49 | + storage: multer.diskStorage({ | ||
50 | + destination(req, file, cb) { | ||
51 | + cb(null, 'uploads/'); | ||
52 | + }, | ||
53 | + filename(req, file, cb) { | ||
54 | + const ext = path.extname(file.originalname); | ||
55 | + cb(null, path.basename(file.originalname, ext) + new Date().valueOf() + ext); | ||
56 | + }, | ||
57 | + }), | ||
58 | + limits: { fileSize: 5 * 1024 * 1024 }, | ||
59 | +}); | ||
60 | +router.post('/good', isLoggedIn, upload.single('img'), async (req, res, next) => { | ||
61 | + try { | ||
62 | + const { name, price } = req.body; | ||
63 | + const good = await Good.create({ | ||
64 | + ownerId: req.user.id, | ||
65 | + name, | ||
66 | + img: req.file.filename, | ||
67 | + price, | ||
68 | + }); | ||
69 | + const end = new Date(); | ||
70 | + end.setDate(end.getDate() + 1); // 하루 뒤 | ||
71 | + schedule.scheduleJob(end, async () => { | ||
72 | + const success = await Auction.find({ | ||
73 | + where: { goodId: good.id }, | ||
74 | + order: [['bid', 'DESC']], | ||
75 | + }); | ||
76 | + await Good.update({ soldId: success.userId }, { where: { id: good.id } }); | ||
77 | + await User.update({ | ||
78 | + money: sequelize.literal(`money - ${success.bid}`), | ||
79 | + }, { | ||
80 | + where: { id: success.userId }, | ||
81 | + }); | ||
82 | + }); | ||
83 | + res.redirect('/'); | ||
84 | + } catch (error) { | ||
85 | + console.error(error); | ||
86 | + next(error); | ||
87 | + } | ||
88 | +}); | ||
89 | + | ||
90 | +router.get('/good/:id', isLoggedIn, async (req, res, next) => { | ||
91 | + try { | ||
92 | + const [good, auction] = await Promise.all([ | ||
93 | + Good.find({ | ||
94 | + where: { id: req.params.id }, | ||
95 | + include: { | ||
96 | + model: User, | ||
97 | + as: 'owner', | ||
98 | + }, | ||
99 | + }), | ||
100 | + Auction.findAll({ | ||
101 | + where: { goodId: req.params.id }, | ||
102 | + include: { model: User }, | ||
103 | + order: [['bid', 'ASC']], | ||
104 | + }), | ||
105 | + ]); | ||
106 | + res.render('auction', { | ||
107 | + title: `${good.name} - NodeAuction`, | ||
108 | + good, | ||
109 | + auction, | ||
110 | + auctionError: req.flash('auctionError'), | ||
111 | + }); | ||
112 | + } catch (error) { | ||
113 | + console.error(error); | ||
114 | + next(error); | ||
115 | + } | ||
116 | +}); | ||
117 | + | ||
118 | +router.post('/good/:id/bid', isLoggedIn, async (req, res, next) => { | ||
119 | + try { | ||
120 | + const { bid, msg } = req.body; | ||
121 | + const good = await Good.find({ | ||
122 | + where: { id: req.params.id }, | ||
123 | + include: { model: Auction }, | ||
124 | + order: [[{ model: Auction }, 'bid', 'DESC']], | ||
125 | + }); | ||
126 | + if (good.price > bid) { // 시작 가격보다 낮게 입찰하면 | ||
127 | + return res.status(403).send('시작 가격보다 높게 입찰해야 합니다.'); | ||
128 | + } | ||
129 | + // 경매 종료 시간이 지났으면 | ||
130 | + if (new Date(good.createdAt).valueOf() + (24 * 60 * 60 * 1000) < new Date()) { | ||
131 | + return res.status(403).send('경매가 이미 종료되었습니다'); | ||
132 | + } | ||
133 | + // 직전 입찰가와 현재 입찰가 비교 | ||
134 | + if (good.auctions[0] && good.auctions[0].bid >= bid) { | ||
135 | + return res.status(403).send('이전 입찰가보다 높아야 합니다'); | ||
136 | + } | ||
137 | + const result = await Auction.create({ | ||
138 | + bid, | ||
139 | + msg, | ||
140 | + userId: req.user.id, | ||
141 | + goodId: req.params.id, | ||
142 | + }); | ||
143 | + req.app.get('io').to(req.params.id).emit('bid', { | ||
144 | + bid: result.bid, | ||
145 | + msg: result.msg, | ||
146 | + nick: req.user.nick, | ||
147 | + }); | ||
148 | + return res.send('ok'); | ||
149 | + } catch (error) { | ||
150 | + console.error(error); | ||
151 | + return next(error); | ||
152 | + } | ||
153 | +}); | ||
154 | + | ||
155 | +router.get('/list', isLoggedIn, async (req, res, next) => { | ||
156 | + try { | ||
157 | + const goods = await Good.findAll({ | ||
158 | + where: { soldId: req.user.id }, | ||
159 | + include: { model: Auction }, | ||
160 | + order: [[{ model: Auction }, 'bid', 'DESC']], | ||
161 | + }); | ||
162 | + res.render('list', { title: '낙찰 목록 - NodeAuction', goods }); | ||
163 | + } catch (error) { | ||
164 | + console.error(error); | ||
165 | + next(error); | ||
166 | + } | ||
167 | +}); | ||
168 | + | ||
169 | +module.exports = router; |
routes/middlewares.js
0 → 100644
1 | +exports.isLoggedIn = (req, res, next) => { | ||
2 | + if (req.isAuthenticated()) { | ||
3 | + next(); | ||
4 | + } else { | ||
5 | + req.flash('loginError', '로그인이 필요합니다'); | ||
6 | + res.redirect('/'); | ||
7 | + } | ||
8 | +}; | ||
9 | + | ||
10 | +exports.isNotLoggedIn = (req, res, next) => { | ||
11 | + if (!req.isAuthenticated()) { | ||
12 | + next(); | ||
13 | + } else { | ||
14 | + res.redirect('/'); | ||
15 | + } | ||
16 | +}; |
-
Please register or login to post a comment