Showing
26 changed files
with
1018 additions
and
21 deletions
.gitignore
0 → 100644
README.md
deleted
100644 → 0
app.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var app = express(); | ||
3 | +var port = 3000; | ||
4 | + | ||
5 | +var mongoose = require('mongoose'); | ||
6 | +mongoose.Promise = global.Promise; | ||
7 | +//auto-increment를 위한 패키지 | ||
8 | +var path = require('path'); | ||
9 | +var logger = require('morgan'); | ||
10 | +var bodyParser = require('body-parser'); | ||
11 | +var cookieParser = require('cookie-parser'); | ||
12 | +var flash = require('connect-flash'); | ||
13 | +//passport 로그인 관련 | ||
14 | +var passport = require('passport'); | ||
15 | +var session = require('express-session'); | ||
16 | + | ||
17 | +var db = mongoose.connection; | ||
18 | +db.on('error', console.error); | ||
19 | +db.once('open', function () { | ||
20 | + console.log('mongo db Connection'); | ||
21 | +}); | ||
22 | +var connect = mongoose.connect('mongodb://127.0.0.1:27017/cookBook', { | ||
23 | + useMongoClient: true, | ||
24 | +}); | ||
25 | + | ||
26 | +//admin module get | ||
27 | +var admin = require('./routes/admin'); | ||
28 | +var accounts = require('./routes/accounts'); | ||
29 | +var auth = require('./routes/auth'); | ||
30 | +var home = require('./routes/home.js'); | ||
31 | +var chat = require('./routes/chat'); | ||
32 | +var connectMongo = require('connect-mongo'); | ||
33 | +var MongoStore = connectMongo(session); | ||
34 | + | ||
35 | +app.set('views', path.join(__dirname, 'views')); | ||
36 | +app.set('view engine', 'ejs'); | ||
37 | +app.use(logger('dev')); | ||
38 | +app.use(bodyParser.json()); | ||
39 | +app.use(bodyParser.urlencoded({ extended: false })); | ||
40 | +app.use(cookieParser()); | ||
41 | +app.use('/uploads', express.static('uploads')); | ||
42 | + | ||
43 | +var sessionMiddleWare = session({ | ||
44 | + secret: 'fastcampus', | ||
45 | + resave: false, | ||
46 | + saveUninitialized: true, | ||
47 | + cookie: { | ||
48 | + maxAge: 2000 * 60 * 60, //지속시간 2시간 | ||
49 | + }, | ||
50 | + store: new MongoStore({ | ||
51 | + mongooseConnection: mongoose.connection, | ||
52 | + ttl: 14 * 24 * 60 * 60, | ||
53 | + }), | ||
54 | +}); | ||
55 | +app.use(sessionMiddleWare); | ||
56 | + | ||
57 | +//passport 적용 | ||
58 | +app.use(passport.initialize()); | ||
59 | +app.use(passport.session()); | ||
60 | + | ||
61 | +//플래시 메시지 관련 | ||
62 | +app.use(flash()); | ||
63 | + | ||
64 | +app.use(function (req, res, next) { | ||
65 | + app.locals.isLogin = req.isAuthenticated(); | ||
66 | + //app.locals.urlparameter = req.url; //현재 url 정보를 보내고 싶으면 이와같이 셋팅 | ||
67 | + //app.locals.userData = req.user; //사용 정보를 보내고 싶으면 이와같이 셋팅 | ||
68 | + next(); | ||
69 | +}); | ||
70 | + | ||
71 | +//routes add | ||
72 | +app.use('/', home); | ||
73 | +app.use('/admin', admin); | ||
74 | +app.use('/accounts', accounts); | ||
75 | +app.use('/auth', auth); | ||
76 | +app.use('/chat', chat); | ||
77 | + | ||
78 | +var server = app.listen(port, function () { | ||
79 | + console.log('Express listening on port', port); | ||
80 | +}); | ||
81 | + | ||
82 | +var listen = require('socket.io'); | ||
83 | +var io = listen(server); | ||
84 | +//socket io passport 접근하기 위한 미들웨어 적용 | ||
85 | +io.use(function (socket, next) { | ||
86 | + sessionMiddleWare(socket.request, socket.request.res, next); | ||
87 | +}); | ||
88 | +require('./libs/socketConnection')(io); |
libs/loginRequired.js
0 → 100644
libs/passwordHash.js
0 → 100644
libs/removeByValue.js
0 → 100644
libs/socketConnection.js
0 → 100644
1 | +require('./removeByValue')(); | ||
2 | +var userList = []; //사용자 리스트를 저장할곳 | ||
3 | +module.exports = function(io) { | ||
4 | + io.on('connection', function(socket){ | ||
5 | + | ||
6 | + var session = socket.request.session.passport; | ||
7 | + var user = (typeof session !== 'undefined') ? ( session.user ) : ""; | ||
8 | + | ||
9 | + // userList 필드에 사용자 명이 존재 하지 않으면 삽입 | ||
10 | + if(userList.indexOf(user.displayname) === -1){ | ||
11 | + userList.push(user.displayname); | ||
12 | + } | ||
13 | + io.emit('join', userList); | ||
14 | + | ||
15 | + //사용자 명과 메시지를 같이 반환한다. | ||
16 | + socket.on('client message', function(data){ | ||
17 | + io.emit('server message', { message : data.message , displayname : user.displayname }); | ||
18 | + }); | ||
19 | + | ||
20 | + socket.on('disconnect', function(){ | ||
21 | + userList.removeByValue(user.displayname); | ||
22 | + io.emit('leave', userList); | ||
23 | + }); | ||
24 | + | ||
25 | + }); | ||
26 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
main-page.html
deleted
100644 → 0
1 | -<!DOCTYPE html> | ||
2 | -<html> | ||
3 | - | ||
4 | -<head> | ||
5 | - <title>Hello OSS!</title> | ||
6 | -</head> | ||
7 | - | ||
8 | -<body> | ||
9 | - <header> | ||
10 | - <center> | ||
11 | - <h1>나의 요리비급서 from YouTube</h1> | ||
12 | - </center> | ||
13 | - <hr align="center" width=50%> | ||
14 | - </hr> | ||
15 | - </header> | ||
16 | -</body> | ||
17 | - | ||
18 | -</html> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
models/ProductsModel.js
0 → 100644
1 | +var mongoose = require('mongoose'); | ||
2 | +var Schema = mongoose.Schema; | ||
3 | + | ||
4 | +//생성될 필드명을 정한다. | ||
5 | +var ProductsSchema = new Schema({ | ||
6 | + name: { | ||
7 | + //제품명 | ||
8 | + type: String, | ||
9 | + required: [true, '제목은 입력해주세요'], | ||
10 | + }, | ||
11 | + thumbnail: String, //이미지 파일명 | ||
12 | + price: Number, //가격 | ||
13 | + description: String, //설명 | ||
14 | + created_at: { | ||
15 | + //작성일 | ||
16 | + type: Date, | ||
17 | + default: Date.now(), | ||
18 | + }, | ||
19 | + username: String, //작성자추가 | ||
20 | +}); | ||
21 | + | ||
22 | +ProductsSchema.virtual('getDate').get(function () { | ||
23 | + var date = new Date(this.created_at); | ||
24 | + return { | ||
25 | + year: date.getFullYear(), | ||
26 | + month: date.getMonth() + 1, | ||
27 | + day: date.getDate(), | ||
28 | + }; | ||
29 | +}); | ||
30 | + | ||
31 | +// 1씩 증가하는 primary Key를 만든다 | ||
32 | +// model : 생성할 document 이름 | ||
33 | +// field : primary key , startAt : 1부터 시작 | ||
34 | + | ||
35 | +module.exports = mongoose.model('products', ProductsSchema); |
models/UserModel.js
0 → 100644
1 | +var mongoose = require('mongoose'); | ||
2 | +var Schema = mongoose.Schema; | ||
3 | +var UserSchema = new Schema({ | ||
4 | + username: { | ||
5 | + type: String, | ||
6 | + required: [true, '아이디는 필수입니다.'], | ||
7 | + }, | ||
8 | + password: { | ||
9 | + type: String, | ||
10 | + required: [true, '패스워드는 필수입니다.'], | ||
11 | + }, | ||
12 | + displayname: String, | ||
13 | + created_at: { | ||
14 | + type: Date, | ||
15 | + default: Date.now(), | ||
16 | + }, | ||
17 | +}); | ||
18 | + | ||
19 | +module.exports = mongoose.model('user', UserSchema); |
models/VideoModels.js
0 → 100644
package.json
0 → 100644
1 | +{ | ||
2 | + "name": "node", | ||
3 | + "version": "1.0.0", | ||
4 | + "description": "my-cookbook-project", | ||
5 | + "main": "index.js", | ||
6 | + "scripts": { | ||
7 | + "test": "echo \"Error: no test specified\" && exit 1", | ||
8 | + "start": "nodemon ./app.js" | ||
9 | + }, | ||
10 | + "dependencies": { | ||
11 | + "body-parser": "^1.18.2", | ||
12 | + "connect-flash": "^0.1.1", | ||
13 | + "connect-mongo": "^2.0.0", | ||
14 | + "cookie-parser": "^1.4.3", | ||
15 | + "csurf": "^1.9.0", | ||
16 | + "ejs": "^2.5.7", | ||
17 | + "express": "^4.15.4", | ||
18 | + "express-session": "^1.15.5", | ||
19 | + "mongodb": "^2.2.36", | ||
20 | + "mongoose": "^4.13.21", | ||
21 | + "morgan": "^1.8.2", | ||
22 | + "multer": "^1.3.0", | ||
23 | + "passport": "^0.4.0", | ||
24 | + "passport-facebook": "^2.1.1", | ||
25 | + "passport-local": "^1.0.0", | ||
26 | + "path": "^0.12.7", | ||
27 | + "socket.io": "^2.0.3" | ||
28 | + } | ||
29 | +} |
routes/accounts.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var router = express.Router(); | ||
3 | +var UserModel = require('../models/UserModel'); | ||
4 | +var passwordHash = require('../libs/passwordHash'); | ||
5 | +var passport = require('passport'); | ||
6 | +var LocalStrategy = require('passport-local').Strategy; | ||
7 | + | ||
8 | +passport.serializeUser(function (user, done) { | ||
9 | + console.log('serializeUser'); | ||
10 | + done(null, user); | ||
11 | +}); | ||
12 | + | ||
13 | +passport.deserializeUser(function (user, done) { | ||
14 | + var result = user; | ||
15 | + result.password = ""; | ||
16 | + console.log('deserializeUser'); | ||
17 | + done(null, result); | ||
18 | +}); | ||
19 | + | ||
20 | +passport.use(new LocalStrategy({ | ||
21 | + usernameField: 'username', | ||
22 | + passwordField : 'password', | ||
23 | + passReqToCallback : true | ||
24 | + }, | ||
25 | + function (req, username, password, done) { | ||
26 | + UserModel.findOne({ username : username , password : passwordHash(password) }, function (err,user) { | ||
27 | + if (!user){ | ||
28 | + return done(null, false, { message: '아이디 또는 비밀번호 오류 입니다.' }); | ||
29 | + }else{ | ||
30 | + return done(null, user ); | ||
31 | + } | ||
32 | + }); | ||
33 | + } | ||
34 | +)); | ||
35 | + | ||
36 | +router.get('/', function(req, res){ | ||
37 | + res.send('account app'); | ||
38 | +}); | ||
39 | + | ||
40 | +router.get('/join', function(req, res){ | ||
41 | + res.render('accounts/join'); | ||
42 | +}); | ||
43 | + | ||
44 | +router.post('/join', function(req, res){ | ||
45 | + var User = new UserModel({ | ||
46 | + username : req.body.username, | ||
47 | + password : passwordHash(req.body.password), | ||
48 | + displayname : req.body.displayname | ||
49 | + }); | ||
50 | + User.save(function(err){ | ||
51 | + res.send('<script>alert("회원가입 성공");location.href="/accounts/login";</script>'); | ||
52 | + }); | ||
53 | +}); | ||
54 | + | ||
55 | +router.get('/login', function(req, res){ | ||
56 | + res.render('accounts/login', { flashMessage : req.flash().error }); | ||
57 | +}); | ||
58 | + | ||
59 | +router.post('/login' , | ||
60 | +passport.authenticate('local', { | ||
61 | + failureRedirect: '/accounts/login', | ||
62 | + failureFlash: true | ||
63 | +}), | ||
64 | +function(req, res){ | ||
65 | + res.send('<script>alert("로그인 성공");location.href="/";</script>'); | ||
66 | +} | ||
67 | +); | ||
68 | + | ||
69 | +router.get('/success', function(req, res){ | ||
70 | + res.send(req.user); | ||
71 | +}); | ||
72 | + | ||
73 | + | ||
74 | +router.get('/logout', function(req, res){ | ||
75 | + req.logout(); | ||
76 | + res.redirect('/accounts/login'); | ||
77 | +}); | ||
78 | + | ||
79 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
routes/admin.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var router = express.Router(); | ||
3 | +var ProductsModel = require('../models/ProductsModel'); | ||
4 | +var CommentsModel = require('../models/VideoModels'); | ||
5 | +var csrf = require('csurf'); | ||
6 | +var csrfProtection = csrf({ cookie: true }); | ||
7 | +var loginRequired = require('../libs/loginRequired'); | ||
8 | + | ||
9 | +var path = require('path'); | ||
10 | +var uploadDir = path.join(__dirname, '../uploads'); // 루트의 uploads위치에 저장한다. | ||
11 | +var fs = require('fs'); | ||
12 | + | ||
13 | +//multer 셋팅 | ||
14 | +var multer = require('multer'); | ||
15 | +var storage = multer.diskStorage({ | ||
16 | + destination: function (req, file, callback) { | ||
17 | + //이미지가 저장되는 도착지 지정 | ||
18 | + callback(null, uploadDir); | ||
19 | + }, | ||
20 | + filename: function (req, file, callback) { | ||
21 | + // products-날짜.jpg(png) 저장 | ||
22 | + callback( | ||
23 | + null, | ||
24 | + 'products-' + Date.now() + '.' + file.mimetype.split('/')[1] | ||
25 | + ); | ||
26 | + }, | ||
27 | +}); | ||
28 | + | ||
29 | +var upload = multer({ storage: storage }); | ||
30 | + | ||
31 | +router.get('/', function (req, res) { | ||
32 | + res.send('admin main page'); | ||
33 | +}); | ||
34 | + | ||
35 | +router.get('/products', function (req, res) { | ||
36 | + ProductsModel.find(function (err, products) { | ||
37 | + res.render( | ||
38 | + 'admin/products', | ||
39 | + { products: products } | ||
40 | + //ProductModel의 products를 받아서 | ||
41 | + //admin/products로 response를 보낸다. | ||
42 | + ); | ||
43 | + }); | ||
44 | +}); | ||
45 | + | ||
46 | +router.get( | ||
47 | + '/products/write', | ||
48 | + loginRequired, | ||
49 | + csrfProtection, | ||
50 | + function (req, res) { | ||
51 | + //edit에서도 같은 form을 사용하므로 빈 변수( product )를 넣어서 에러를 피해준다 | ||
52 | + res.render('admin/form', { product: '', csrfToken: req.csrfToken() }); | ||
53 | + } | ||
54 | +); | ||
55 | + | ||
56 | +router.post( | ||
57 | + '/products/write', | ||
58 | + upload.single('thumbnail'), | ||
59 | + loginRequired, | ||
60 | + csrfProtection, | ||
61 | + function (req, res) { | ||
62 | + var product = new ProductsModel({ | ||
63 | + name: req.body.name, | ||
64 | + thumbnail: req.file ? req.file.filename : '', | ||
65 | + price: req.body.price, | ||
66 | + description: req.body.description, | ||
67 | + username: req.user.username, | ||
68 | + }); | ||
69 | + //이 아래는 수정되지 않았음 | ||
70 | + var validationError = product.validateSync(); | ||
71 | + if (validationError) { | ||
72 | + res.send(validationError); | ||
73 | + } else { | ||
74 | + product.save(function (err) { | ||
75 | + res.redirect('/admin/products'); | ||
76 | + }); | ||
77 | + } | ||
78 | + //이 위는 수정되지 않았음 | ||
79 | + } | ||
80 | +); | ||
81 | + | ||
82 | +router.get('/products/detail/:id', function (req, res) { | ||
83 | + //url 에서 변수 값을 받아올떈 req.params.id 로 받아온다 | ||
84 | + ProductsModel.findOne({ id: req.params.id }, function (err, product) { | ||
85 | + //제품정보를 받고 그안에서 댓글을 받아온다. | ||
86 | + CommentsModel.find({ product_id: req.params.id }, function (err, comments) { | ||
87 | + res.render('admin/productsDetail', { | ||
88 | + product: product, | ||
89 | + comments: comments, | ||
90 | + }); | ||
91 | + }); | ||
92 | + }); | ||
93 | +}); | ||
94 | + | ||
95 | +router.get( | ||
96 | + '/products/edit/:id', | ||
97 | + loginRequired, | ||
98 | + csrfProtection, | ||
99 | + function (req, res) { | ||
100 | + //기존에 폼에 value안에 값을 셋팅하기 위해 만든다. | ||
101 | + ProductsModel.findOne({ id: req.params.id }, function (err, product) { | ||
102 | + res.render('admin/form', { | ||
103 | + product: product, | ||
104 | + csrfToken: req.csrfToken(), | ||
105 | + }); | ||
106 | + }); | ||
107 | + } | ||
108 | +); | ||
109 | + | ||
110 | +router.post( | ||
111 | + '/products/edit/:id', | ||
112 | + loginRequired, | ||
113 | + upload.single('thumbnail'), | ||
114 | + csrfProtection, | ||
115 | + function (req, res) { | ||
116 | + //그전에 지정되 있는 파일명을 받아온다 | ||
117 | + ProductsModel.findOne({ id: req.params.id }, function (err, product) { | ||
118 | + //아래의 코드만 추가되면 된다. | ||
119 | + if (req.file && product.thumbnail) { | ||
120 | + //요청중에 파일이 존재 할시 이전이미지 지운다. | ||
121 | + fs.unlinkSync(uploadDir + '/' + product.thumbnail); | ||
122 | + } | ||
123 | + //위의 코드만 추가되면 된다. | ||
124 | + //넣을 변수 값을 셋팅한다 | ||
125 | + var query = { | ||
126 | + name: req.body.name, | ||
127 | + thumbnail: req.file ? req.file.filename : product.thumbnail, | ||
128 | + price: req.body.price, | ||
129 | + description: req.body.description, | ||
130 | + }; | ||
131 | + ProductsModel.update( | ||
132 | + { id: req.params.id }, | ||
133 | + { $set: query }, | ||
134 | + function (err) { | ||
135 | + res.redirect('/admin/products/detail/' + req.params.id); | ||
136 | + } | ||
137 | + ); | ||
138 | + }); | ||
139 | + } | ||
140 | +); | ||
141 | + | ||
142 | +router.get('/products/delete/:id', function (req, res) { | ||
143 | + ProductsModel.remove({ id: req.params.id }, function (err) { | ||
144 | + res.redirect('/admin/products'); | ||
145 | + }); | ||
146 | +}); | ||
147 | + | ||
148 | +router.post('/products/ajax_comment/insert', function (req, res) { | ||
149 | + var comment = new CommentsModel({ | ||
150 | + content: req.body.content, | ||
151 | + product_id: parseInt(req.body.product_id), | ||
152 | + }); | ||
153 | + comment.save(function (err, comment) { | ||
154 | + res.json({ | ||
155 | + id: comment.id, | ||
156 | + content: comment.content, | ||
157 | + message: 'success', | ||
158 | + }); | ||
159 | + }); | ||
160 | +}); | ||
161 | + | ||
162 | +router.post('/products/ajax_comment/delete', function (req, res) { | ||
163 | + CommentsModel.remove({ id: req.body.comment_id }, function (err) { | ||
164 | + res.json({ message: 'success' }); | ||
165 | + }); | ||
166 | +}); | ||
167 | + | ||
168 | +module.exports = router; |
routes/auth.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var router = express.Router(); | ||
3 | +var UserModel = require('../models/UserModel'); | ||
4 | +var passport = require('passport'); | ||
5 | +var FacebookStrategy = require('passport-facebook').Strategy; | ||
6 | + | ||
7 | +passport.serializeUser(function (user, done) { | ||
8 | + done(null, user); | ||
9 | +}); | ||
10 | + | ||
11 | +passport.deserializeUser(function (user, done) { | ||
12 | + done(null, user); | ||
13 | +}); | ||
14 | + | ||
15 | +passport.use(new FacebookStrategy({ | ||
16 | + // https://developers.facebook.com에서 appId 및 scretID 발급 | ||
17 | + clientID: "863352300499259", //입력하세요 | ||
18 | + clientSecret: "36867723fbdd49dac987f9a061e2206a", //입력하세요. | ||
19 | + callbackURL: "http://localhost:3000/auth/facebook/callback", | ||
20 | + profileFields: ['id', 'displayName', 'photos', 'email'] //받고 싶은 필드 나열 | ||
21 | + }, | ||
22 | + function(accessToken, refreshToken, profile, done) { | ||
23 | + //아래 하나씩 찍어보면서 데이터를 참고해주세요. | ||
24 | + //console.log(profile); | ||
25 | + //console.log(profile.displayName); | ||
26 | + //console.log(profile.emails[0].value); | ||
27 | + //console.log(profile._raw); | ||
28 | + //console.log(profile._json); | ||
29 | + UserModel.findOne({ username : "fb_" + profile.id }, function(err, user){ | ||
30 | + if(!user){ //없으면 회원가입 후 로그인 성공페이지 이동 | ||
31 | + var regData = { //DB에 등록 및 세션에 등록될 데이터 | ||
32 | + username : "fb_" + profile.id, | ||
33 | + password : "facebook_login", | ||
34 | + displayname : profile.displayName | ||
35 | + }; | ||
36 | + var User = new UserModel(regData); | ||
37 | + User.save(function(err){ //DB저장 | ||
38 | + done(null,regData); //세션 등록 | ||
39 | + }); | ||
40 | + }else{ //있으면 DB에서 가져와서 세션등록 | ||
41 | + done(null,user); | ||
42 | + } | ||
43 | + | ||
44 | + }); | ||
45 | + } | ||
46 | +)); | ||
47 | + | ||
48 | +// http://localhost:3000/auth/facebook 접근시 facebook으로 넘길 url 작성해줌 | ||
49 | +router.get('/facebook', passport.authenticate('facebook', { scope: 'email'}) ); | ||
50 | + | ||
51 | + | ||
52 | +//인증후 페이스북에서 이 주소로 리턴해줌. 상단에 적은 callbackURL과 일치 | ||
53 | +router.get('/facebook/callback', | ||
54 | + passport.authenticate('facebook', | ||
55 | + { | ||
56 | + successRedirect: '/', | ||
57 | + failureRedirect: '/auth/facebook/fail' | ||
58 | + } | ||
59 | + ) | ||
60 | +); | ||
61 | +//로그인 성공시 이동할 주소 | ||
62 | +router.get('/facebook/success', function(req,res){ | ||
63 | + res.send(req.user); | ||
64 | +}); | ||
65 | + | ||
66 | +router.get('/facebook/fail', function(req,res){ | ||
67 | + res.send('facebook login fail'); | ||
68 | +}); | ||
69 | + | ||
70 | + | ||
71 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
routes/chat.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var router = express.Router(); | ||
3 | + | ||
4 | +router.get('/', function(req,res){ | ||
5 | + if(!req.isAuthenticated()){ | ||
6 | + res.send('<script>alert("로그인이 필요한 서비스입니다.");location.href="/accounts/login";</script>'); | ||
7 | + }else{ | ||
8 | + res.render('chat/index'); | ||
9 | + } | ||
10 | +}); | ||
11 | + | ||
12 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
routes/home.js
0 → 100644
1 | +var express = require('express'); | ||
2 | +var router = express.Router(); | ||
3 | +var ProductsModel = require('../models/ProductsModel'); | ||
4 | + | ||
5 | +/* GET home page. */ | ||
6 | +router.get('/', function(req,res){ | ||
7 | + ProductsModel.find( function(err,products){ //첫번째 인자는 err, 두번째는 받을 변수명 | ||
8 | + res.render( 'home' , | ||
9 | + { products : products } // DB에서 받은 products를 products변수명으로 내보냄 | ||
10 | + ); | ||
11 | + }); | ||
12 | +}); | ||
13 | + | ||
14 | +module.exports = router; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/accounts/join.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | +<div class="row"> | ||
3 | + <div class="col-md-4 col-md-offset-4"> | ||
4 | + <div class="login-panel panel panel-default"> | ||
5 | + <div class="panel-heading"> | ||
6 | + <h3 class="panel-title">회원가입</h3> | ||
7 | + </div> | ||
8 | + <div class="panel-body"> | ||
9 | + <form role="form" action="" id="join_form" method="post"> | ||
10 | + <fieldset> | ||
11 | + <div class="form-group"> | ||
12 | + <input class="form-control" placeholder="ID" name="username" type="text" autofocus="" required=""> | ||
13 | + </div> | ||
14 | + <div class="form-group"> | ||
15 | + <input class="form-control" placeholder="Password" name="password" type="password" value="" required=""> | ||
16 | + </div> | ||
17 | + <div class="form-group"> | ||
18 | + <input class="form-control" placeholder="Password 확인" name="password2" type="password" value="" required=""> | ||
19 | + </div> | ||
20 | + <div class="form-group"> | ||
21 | + <input class="form-control" placeholder="이름" name="displayname" type="text" value="" required=""> | ||
22 | + </div> | ||
23 | + <!-- Change this to a button or input when using this as a form --> | ||
24 | + <input type="submit" class="btn btn-lg btn-success btn-block" value="가입하기"> | ||
25 | + <div style="margin-top: 10px"> | ||
26 | + <a href="/auth/facebook" class="btn btn-lg btn-primary btn-block"> | ||
27 | + <i class="fa fa-facebook" aria-hidden="true"></i> 페이스북 회원가입 | ||
28 | + </a> | ||
29 | + </div> | ||
30 | + </fieldset> | ||
31 | + </form> | ||
32 | + </div> | ||
33 | + </div> | ||
34 | + </div> | ||
35 | +</div> | ||
36 | +<script type="text/javascript"> | ||
37 | +(function(){ | ||
38 | + $(document).ready(function() { | ||
39 | + $('#join_form').submit(function(){ | ||
40 | + var $usernameInput = $('#join_form input[name=username]'); | ||
41 | + var $passwordInput = $('#join_form input[name=password]'); | ||
42 | + var $passwordInput2 = $('#join_form input[name=password2]'); | ||
43 | + var $displayname = $('#join_form input[name=displayname]'); | ||
44 | + if(!$usernameInput.val()){ | ||
45 | + alert("아이디를 입력해주세요."); | ||
46 | + $usernameInput.focus(); | ||
47 | + return false; | ||
48 | + } | ||
49 | + if(!$passwordInput.val()){ | ||
50 | + alert("패스워드를 입력해주세요."); | ||
51 | + $passwordInput.focus(); | ||
52 | + return false; | ||
53 | + } | ||
54 | + if(!$passwordInput2.val()){ | ||
55 | + alert("확인 패스워드를 입력해주세요."); | ||
56 | + $passwordInput2.focus(); | ||
57 | + return false; | ||
58 | + } | ||
59 | + if(!$displayname.val()){ | ||
60 | + alert("이름을 입력해주세요."); | ||
61 | + $displayname.focus(); | ||
62 | + return false; | ||
63 | + } | ||
64 | + if($passwordInput.val() !== $passwordInput2.val()){ | ||
65 | + alert("패스워드와 확인용패스워드를 똑같이 입력해주세요."); | ||
66 | + return false; | ||
67 | + } | ||
68 | + return true; | ||
69 | + }); | ||
70 | + }); | ||
71 | +})(); | ||
72 | +</script> | ||
73 | +<% include ../includes/footer.ejs %> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/accounts/login.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | +<div class="row"> | ||
3 | + <div class="col-md-4 col-md-offset-4"> | ||
4 | + <%if(typeof flashMessage !=='undefined') {%> | ||
5 | + <div class="alert alert-danger" role="alert"><%=flashMessage%></div> | ||
6 | + <%}%> | ||
7 | + <div class="login-panel panel panel-default"> | ||
8 | + <div class="panel-heading"> | ||
9 | + <h3 class="panel-title">로그인</h3> | ||
10 | + </div> | ||
11 | + <div class="panel-body"> | ||
12 | + <form role="form" action="" id="login_form" method="post"> | ||
13 | + <fieldset> | ||
14 | + <div class="form-group"> | ||
15 | + <input class="form-control" placeholder="ID" name="username" type="text" autofocus="" required=""> | ||
16 | + </div> | ||
17 | + <div class="form-group"> | ||
18 | + <input class="form-control" placeholder="Password" name="password" type="password" value="" required=""> | ||
19 | + </div> | ||
20 | + <!-- Change this to a button or input when using this as a form --> | ||
21 | + <input type="submit" class="btn btn-lg btn-success btn-block" value="로그인"> | ||
22 | + <div style="margin-top: 10px"> | ||
23 | + <a href="/auth/facebook" class="btn btn-lg btn-primary btn-block"> | ||
24 | + <i class="fa fa-facebook" aria-hidden="true"></i> 페이스북 로그인 | ||
25 | + </a> | ||
26 | + </div> | ||
27 | + </fieldset> | ||
28 | + </form> | ||
29 | + </div> | ||
30 | + </div> | ||
31 | + </div> | ||
32 | +</div> | ||
33 | +<script type="text/javascript"> | ||
34 | +(function(){ | ||
35 | + $(document).ready(function() { | ||
36 | + $('#login_form').submit(function(){ | ||
37 | + var $usernameInput = $('#login_form input[name=username]'); | ||
38 | + var $passwordInput = $('#login_form input[name=password]'); | ||
39 | + if(!$usernameInput.val()){ | ||
40 | + alert("아이디를 입력해주세요."); | ||
41 | + $usernameInput.focus(); | ||
42 | + return false; | ||
43 | + } | ||
44 | + if(!$passwordInput.val()){ | ||
45 | + alert("패스워드를 입력해주세요."); | ||
46 | + $passwordInput.focus(); | ||
47 | + return false; | ||
48 | + } | ||
49 | + return true; | ||
50 | + }); | ||
51 | + }); | ||
52 | +})(); | ||
53 | +</script> | ||
54 | +<% include ../includes/footer.ejs %> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/admin/form.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | + <form action="" method="post" enctype="multipart/form-data"> | ||
3 | + <input type="hidden" name="_csrf" value="<%=csrfToken%>" /> | ||
4 | + <table class="table table-bordered"> | ||
5 | + <tr> | ||
6 | + <th>제품명</th> | ||
7 | + <td><input type="text" name="name" class="form-control" value="<%=product.name%>"/></td> | ||
8 | + </tr> | ||
9 | + <tr> | ||
10 | + <th>섬네일</th> | ||
11 | + <td> | ||
12 | + <input type="file" name="thumbnail" /> | ||
13 | + <% if(product.thumbnail){ %> | ||
14 | + <a href="/uploads/<%=product.thumbnail%>" target="_blank">업로드 이미지 보기</a> | ||
15 | + <% } %> | ||
16 | + </td> | ||
17 | + </tr> | ||
18 | + <tr> | ||
19 | + <th>가격</th> | ||
20 | + <td><input type="text" name="price" class="form-control" value="<%=product.price%>"/></td> | ||
21 | + </tr> | ||
22 | + <tr> | ||
23 | + <th>설명</th> | ||
24 | + <td><input type="text" name="description" class="form-control" value="<%=product.description%>"/></td> | ||
25 | + </tr> | ||
26 | + </table> | ||
27 | + <button class="btn btn-primary">작성하기</button> | ||
28 | + </form> | ||
29 | +<% include ../includes/footer.ejs %> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/admin/products.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | + <table class="table table-bordered table-hover"> | ||
3 | + <tr> | ||
4 | + <th>제목</th> | ||
5 | + <th>작성일</th> | ||
6 | + <th>삭제</th> | ||
7 | + </tr> | ||
8 | + <%products.forEach(function(product){%> | ||
9 | + <tr> | ||
10 | + <td> | ||
11 | + <a href="/admin/products/detail/<%=product.id%>"><%=product.name%></a> | ||
12 | + </td> | ||
13 | + <td> | ||
14 | + <%=product.getDate.year%> - | ||
15 | + <%=product.getDate.month%> - | ||
16 | + <%=product.getDate.day%> | ||
17 | + </td> | ||
18 | + <td> | ||
19 | + <a href="/admin/products/delete/<%=product.id%>" class="btn btn-danger" onclick="return confirm('삭제하시겠습니까?')">삭제</a> | ||
20 | + </td> | ||
21 | + </tr> | ||
22 | + <% }); %> | ||
23 | + </table> | ||
24 | + | ||
25 | + <a href="/admin/products/write" class="btn btn-default">작성하기</a> | ||
26 | + | ||
27 | +<% include ../includes/footer.ejs %> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/admin/productsDetail.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | + <div class="panel panel-default"> | ||
3 | + <div class="panel-heading"> | ||
4 | + <%=product.name%> | ||
5 | + </div> | ||
6 | + <div class="panel-body"> | ||
7 | + <div style="padding-bottom: 10px"> | ||
8 | + 작성일 : | ||
9 | + <%=product.getDate.year%> - | ||
10 | + <%=product.getDate.month%> - | ||
11 | + <%=product.getDate.day%> | ||
12 | + </div> | ||
13 | + <% if(product.thumbnail){%> | ||
14 | + <p> | ||
15 | + <img src="/uploads/<%=product.thumbnail%>" style="max-width: 100%"/> | ||
16 | + </p> | ||
17 | + <% } %> | ||
18 | + <%=product.description%> | ||
19 | + <!-- 댓글영역 --> | ||
20 | + <div> | ||
21 | + 댓글작성하기 | ||
22 | + <form id="commentForm" action="" method="post"> | ||
23 | + <input type="hidden" name="product_id" value="<%=product.id%>" /> | ||
24 | + <textarea class="form-control" name="content"></textarea> | ||
25 | + <button class="btn btn-primary" style="margin-top: 10px">댓글작성</button> | ||
26 | + </form> | ||
27 | + </div> | ||
28 | + <!-- 댓글영역 --> | ||
29 | + <hr /> | ||
30 | + <div id="comment_area"> | ||
31 | + <% comments.forEach(function(comment){ %> | ||
32 | + <div> | ||
33 | + <%=comment.content%> | ||
34 | + ( <a class='comment_delete' comment_id='<%=comment.id%>'>삭제</a> ) | ||
35 | + </div> | ||
36 | + <% }); %> | ||
37 | + </div> | ||
38 | + </div> | ||
39 | + </div> | ||
40 | + | ||
41 | + <a href="/admin/products" class="btn btn-default">목록으로</a> | ||
42 | + <a href="/admin/products/edit/<%=product.id%>" class="btn btn-primary">수정</a> | ||
43 | +<% include ../includes/footer.ejs %> | ||
44 | +<script> | ||
45 | +(function(){ | ||
46 | + $(document).ready(function() { | ||
47 | + $('#commentForm').submit(function(){ | ||
48 | + var $contentVal = $(this).children('textarea[name=content]').val(); | ||
49 | + if($contentVal){ | ||
50 | + $.ajax({ | ||
51 | + url: '/admin/products/ajax_comment/insert', | ||
52 | + type: 'POST', | ||
53 | + data: $(this).serialize(), | ||
54 | + }) | ||
55 | + .done(function(args) { | ||
56 | + if(args.message==="success"){ | ||
57 | + $('#comment_area').append( | ||
58 | + '<div>' + args.content + | ||
59 | + " ( <a class='comment_delete' comment_id='"+ args.id +"'>삭제</a> ) </div>" | ||
60 | + ); | ||
61 | + $('#commentForm textarea[name=content]').val(""); | ||
62 | + } | ||
63 | + }) | ||
64 | + .fail(function(args) { | ||
65 | + console.log(args); | ||
66 | + }); | ||
67 | + }else{ | ||
68 | + alert('댓글 내용을 입력해주세요.') | ||
69 | + } | ||
70 | + return false; | ||
71 | + }); | ||
72 | + }); | ||
73 | +})(); | ||
74 | +</script> | ||
75 | +<script> | ||
76 | +$(document).on('click' , '.comment_delete' , function(){ | ||
77 | + if(confirm('삭제하시겠습니까?')){ //확인창 예 눌렀을 시만 진행 | ||
78 | + var $self = $(this); | ||
79 | + $.ajax({ | ||
80 | + url: '/admin/products/ajax_comment/delete', | ||
81 | + type: 'POST', | ||
82 | + data: { comment_id : $self.attr('comment_id') }, | ||
83 | + }) | ||
84 | + .done(function() { | ||
85 | + $self.parent().remove(); | ||
86 | + alert("삭제가 완료되었습니다."); | ||
87 | + }) | ||
88 | + .fail(function(args) { | ||
89 | + console.log(args); | ||
90 | + }); | ||
91 | + } | ||
92 | +}); | ||
93 | +</script> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/chat/index.ejs
0 → 100644
1 | +<% include ../includes/header.ejs %> | ||
2 | +<div class="row"> | ||
3 | + <div class="col-sm-10"> | ||
4 | + <div class="panel panel-default" id="chatWrap"> | ||
5 | + <div class="panel-heading">대화내용</div> | ||
6 | + <div class="panel-body"> | ||
7 | + <ul id="chatBody"></ul> | ||
8 | + </div> | ||
9 | + </div> | ||
10 | + </div> | ||
11 | + <div class="col-sm-2"> | ||
12 | + <div class="panel panel-default" id="userWrap"> | ||
13 | + <div class="panel-heading">User</div> | ||
14 | + <div class="panel-body"> | ||
15 | + <ul id="userList"></ul> | ||
16 | + </div> | ||
17 | + </div> | ||
18 | + </div> | ||
19 | +</div> | ||
20 | + | ||
21 | +<div> | ||
22 | + <form action="" method="post" id="sendForm"> | ||
23 | + | ||
24 | + <div class="input-group"> | ||
25 | + <input type="hidden" name="socketId"> | ||
26 | + <input type="text" name="message" class="form-control" placeholder="대화내용을 입력해주세요."> | ||
27 | + <span class="input-group-btn"> | ||
28 | + <button class="btn btn-primary">작성하기</button> | ||
29 | + </span> | ||
30 | + </div><!-- /input-group --> | ||
31 | + | ||
32 | + </form> | ||
33 | + | ||
34 | +</div> | ||
35 | + | ||
36 | +<style type="text/css"> | ||
37 | +.panel-default ul { padding-left:0px; } | ||
38 | +.panel-default ul li { list-style:none; padding-left:0px;} | ||
39 | +.panel-default .panel-body {min-height:350px; max-height:350px; overflow-y:scroll; } | ||
40 | +#chatWrap ul li strong::after { content: " : "; } | ||
41 | +@media (max-width: 768px) { | ||
42 | + #userWrap { display:none; } | ||
43 | + #chatWrap .panel-body { min-height:250px; } | ||
44 | +} | ||
45 | +</style> | ||
46 | +<script src="/socket.io/socket.io.js"></script> | ||
47 | +<script> | ||
48 | +(function(){ | ||
49 | + var socket = io(); | ||
50 | + | ||
51 | + function updateUserList(userList){ | ||
52 | + $('#userList').html(""); | ||
53 | + for(var key in userList){ | ||
54 | + $('#userList').append('<li>' + userList[key] + '</li>'); | ||
55 | + } | ||
56 | + } | ||
57 | + | ||
58 | + socket.on('join', function(data){ | ||
59 | + updateUserList(data); | ||
60 | + }); | ||
61 | + socket.on('server message', function(data){ | ||
62 | + $('#chatBody').append('<li><strong>'+ data.displayname +'</strong>' + data.message + '</li>'); | ||
63 | + }); | ||
64 | + | ||
65 | + socket.on('leave', function(data){ | ||
66 | + updateUserList(data); | ||
67 | + }); | ||
68 | + | ||
69 | + $(document).ready(function() { | ||
70 | + $('#sendForm').submit(function(){ | ||
71 | + var $massage = $('#sendForm input[name=message]'); | ||
72 | + socket.emit('client message', { message : $massage.val()}); | ||
73 | + $massage.val(''); | ||
74 | + return false; | ||
75 | + }); | ||
76 | + }); | ||
77 | +})(); | ||
78 | +</script> | ||
79 | +<% include ../includes/footer.ejs %> | ||
80 | + |
views/home.ejs
0 → 100644
1 | +<% include ./includes/header.ejs %> | ||
2 | + <div class="container" id="masonry_container"> | ||
3 | + <% products.forEach(function(product){ %> | ||
4 | + <div class="masonry-grid"> | ||
5 | + <%if(product.thumbnail){%> | ||
6 | + <img src="/uploads/<%=product.thumbnail%>"> | ||
7 | + <%}%> | ||
8 | + <p> | ||
9 | + <%=product.title%><br /> | ||
10 | + by <%=product.username%>( | ||
11 | + <%=product.getDate.year%>. | ||
12 | + <%=product.getDate.month%>. | ||
13 | + <%=product.getDate.day%> | ||
14 | + ) | ||
15 | + </p> | ||
16 | + </div> | ||
17 | + <%});%> | ||
18 | + </div> | ||
19 | +<style type="text/css"> | ||
20 | +.masonry-grid img { max-width: 260px; } | ||
21 | +</style> | ||
22 | +<script src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js"></script> | ||
23 | +<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/4.1.1/imagesloaded.pkgd.min.js"></script> | ||
24 | +<script type="text/javascript"> | ||
25 | + var $masonry_container = $('#masonry_container'); | ||
26 | + $masonry_container.imagesLoaded(function(){ | ||
27 | + $masonry_container.masonry({ | ||
28 | + itemSelector : '.masonry-grid', | ||
29 | + columnWidth : 270 | ||
30 | + }); | ||
31 | + }); | ||
32 | +</script> | ||
33 | +<% include ./includes/footer.ejs %> | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
views/includes/footer.ejs
0 → 100644
views/includes/header.ejs
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | +<head> | ||
4 | + <meta charset="UTF-8"> | ||
5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
6 | + <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
7 | + <title>Node.js 예제</title> | ||
8 | + <script | ||
9 | + src="https://code.jquery.com/jquery-3.2.1.min.js" | ||
10 | + integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" | ||
11 | + crossorigin="anonymous"></script> | ||
12 | + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> | ||
13 | + <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> | ||
14 | + <!--bootstrap js 추가 --> | ||
15 | + <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> | ||
16 | +</head> | ||
17 | +<body> | ||
18 | + <div class="container" style="padding-top: 10px;"> | ||
19 | + <nav class="navbar navbar-inverse"> | ||
20 | + <div class="container-fluid"> | ||
21 | + <div class="navbar-header"> | ||
22 | + <!-- 오른쪽 메뉴바 --> | ||
23 | + <button type="button" class="collapsed navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-9" aria-expanded="false"> | ||
24 | + <span class="sr-only">Toggle navigation</span> | ||
25 | + <span class="icon-bar"></span> | ||
26 | + <span class="icon-bar"></span> | ||
27 | + <span class="icon-bar"></span> | ||
28 | + </button> | ||
29 | + <a href="/" class="navbar-brand">Nodejs</a> | ||
30 | + </div> | ||
31 | + <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-9"> | ||
32 | + <ul class="nav navbar-nav"> | ||
33 | + <li class="active"> | ||
34 | + <a href="/">Home</a> | ||
35 | + </li> | ||
36 | + <li><a href="/admin/products">ADMIN</a></li> | ||
37 | + <li><a href="/chat">CHAT</a></li> | ||
38 | + <% if(isLogin){%> | ||
39 | + <li><a href="/accounts/logout" onclick="return confirm('로그아웃 하시겠습니까?')">LOGOUT</a></li> | ||
40 | + <%}else{%> | ||
41 | + <li><a href="/accounts/join">JOIN</a></li> | ||
42 | + <li><a href="/accounts/login">LOGIN</a></li> | ||
43 | + <%}%> | ||
44 | + </ul> | ||
45 | + </div> | ||
46 | + </div> | ||
47 | + </nav> |
-
Please register or login to post a comment