임태민

Update Project directory

- 포스팅 구현
- 회원가입, 로그인, 로그아웃 구현
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy; // 1
var User = require('../models/User');
// serialize & deserialize User // 2
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findOne({_id:id}, function(err, user) {
done(err, user);
});
});
// local strategy // 3
passport.use('local-login',
new LocalStrategy({
usernameField : 'username', // 3-1
passwordField : 'password', // 3-1
passReqToCallback : true
},
function(req, username, password, done) { // 3-2
User.findOne({username:username})
.select({password:1})
.exec(function(err, user) {
if (err) return done(err);
if (user && user.authenticate(password)){ // 3-3
return done(null, user);
}
else {
req.flash('username', username);
req.flash('errors', {login:'The username or password is incorrect.'});
return done(null, false);
}
});
}
)
);
module.exports = passport;
\ No newline at end of file
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const methodOverride = require('body-parser');
const app = express();
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var flash = require('connect-flash');
var session = require('express-session');
var passport = require('./config/passport');
//require('./config/passport');
var app = express();
// DB Setting
mongoose.set('useNewUrlParser', true);
......@@ -36,11 +40,32 @@ app.engine('html', require('ejs').renderFile);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(methodOverride('_method'));
app.use(flash());
app.use(session({
secret:'MySecret',
resave:true,
saveUninitialized:true
}));
// Passport
app.use(passport.initialize());
app.use(passport.session());
// Custom Middlewares
app.use(function(req,res,next){
res.locals.isAuthenticated = req.isAuthenticated();
res.locals.currentUser = req.user;
next();
});
// Routes
app.use('/', require('./routes/home'));
app.use('/posts', require('./routes/posts'));
app.use('/users', require('./routes/users'))
// Server
......
var mongoose = require('mongoose');
var bcrypt = require('bcryptjs'); // 1
// schema //1
var userSchema = mongoose.Schema({
username:{
type:String,
required:[true,'Username is required!'],
match:[/^.{4,12}$/,'Should be 4-12 characters!'],
trim:true,
unique:true
},
password:{
type:String,
required:[true,'Password is required!'],
select:false
},
name:{
type:String,
required:[true,'Name is required!'],
match:[/^.{4,12}$/,'Should be 4-12 characters!'],
trim:true
},
email:{
type:String,
match:[/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,'Should be a vaild email address!'],
trim:true
}
},{
toObject:{virtuals:true}
});
// virtuals // 2
userSchema.virtual('passwordConfirmation')
.get(function(){ return this._passwordConfirmation; })
.set(function(value){ this._passwordConfirmation=value; });
userSchema.virtual('originalPassword')
.get(function(){ return this._originalPassword; })
.set(function(value){ this._originalPassword=value; });
userSchema.virtual('currentPassword')
.get(function(){ return this._currentPassword; })
.set(function(value){ this._currentPassword=value; });
userSchema.virtual('newPassword')
.get(function(){ return this._newPassword; })
.set(function(value){ this._newPassword=value; });
// password validation // 2
var passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/;
var passwordRegexErrorMessage = 'Should be minimum 8 characters of alphabet and number combination!';
userSchema.path('password').validate(function(v) {
var user = this;
// create user
if(user.isNew){
if(!user.passwordConfirmation){
user.invalidate('passwordConfirmation', 'Password Confirmation is required.');
}
if(!passwordRegex.test(user.password)){
user.invalidate('password', passwordRegexErrorMessage);
}
else if(user.password !== user.passwordConfirmation) {
user.invalidate('passwordConfirmation', 'Password Confirmation does not matched!');
}
}
// update user
if(!user.isNew){
if(!user.currentPassword){
user.invalidate('currentPassword', 'Current Password is required!');
}
else if(!bcrypt.compareSync(user.currentPassword, user.originalPassword)){
user.invalidate('currentPassword', 'Current Password is invalid!');
}
if(user.newPassword && !passwordRegex.test(user.newPassword)){
user.invalidate("newPassword", passwordRegexErrorMessage);
}
else if(user.newPassword !== user.passwordConfirmation) {
user.invalidate('passwordConfirmation', 'Password Confirmation does not matched!');
}
}
});
userSchema.pre('save', function (next){
var user = this;
if(!user.isModified('password')){ // 3-1
return next();
}
else {
user.password = bcrypt.hashSync(user.password); //3-2
return next();
}
});
// model methods // 4
userSchema.methods.authenticate = function (password) {
var user = this;
return bcrypt.compareSync(password,user.password);
};
// model & export
var User = mongoose.model('user',userSchema);
module.exports = User;
\ No newline at end of file
......@@ -9,11 +9,16 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"connect-flash": "^0.1.1",
"ejs": "^3.1.6",
"express": "^4.17.1",
"express-session": "^1.17.1",
"method-override": "^3.0.0",
"mongoose": "^5.12.8"
"mongoose": "^5.12.8",
"passport": "^0.4.1",
"passport-local": "^1.0.0"
}
},
"node_modules/@types/bson": {
......@@ -76,6 +81,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"node_modules/bl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
......@@ -166,6 +176,14 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/connect-flash": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
"integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
......@@ -320,6 +338,37 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-session": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
"integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
"dependencies": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.0",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express-session/node_modules/safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
},
"node_modules/filelist": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
......@@ -676,6 +725,14 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/optional-require": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
......@@ -692,11 +749,47 @@
"node": ">= 0.8"
}
},
"node_modules/passport": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
"integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
"dependencies": {
"passport-strategy": "1.x.x"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
......@@ -722,6 +815,14 @@
"node": ">=0.6"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
......@@ -898,6 +999,17 @@
"node": ">= 0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
......@@ -983,6 +1095,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"bl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
......@@ -1061,6 +1178,11 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"connect-flash": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
"integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
......@@ -1182,6 +1304,33 @@
"vary": "~1.1.2"
}
},
"express-session": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
"integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.0",
"uid-safe": "~2.1.5"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
}
}
},
"filelist": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
......@@ -1433,6 +1582,11 @@
"ee-first": "1.1.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"optional-require": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz",
......@@ -1443,11 +1597,38 @@
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"passport": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz",
"integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1"
}
},
"passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
......@@ -1467,6 +1648,11 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
......@@ -1618,6 +1804,14 @@
"mime-types": "~2.1.24"
}
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
......
......@@ -10,10 +10,15 @@
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"connect-flash": "^0.1.1",
"ejs": "^3.1.6",
"express": "^4.17.1",
"express-session": "^1.17.1",
"method-override": "^3.0.0",
"mongoose": "^5.12.8"
"mongoose": "^5.12.8",
"passport": "^0.4.1",
"passport-local": "^1.0.0"
}
}
......
body {
font-family: 'Open Sans', sans-serif;
}
h1 {
}
.h1 {
font-family: 'PT Serif', serif;
}
\ No newline at end of file
}
.breadcrumb-item {
font-size: 0.8em !important;
}
.ellipsis{
display: block;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis; /* 1 */
}
.board-table {
table-layout: fixed;
}
.board-table .date {
width: 100px;
}
.post-body{
white-space: pre-line; /* 2 */
}
.post-info{
font-size: 0.8em;
}
.user-form {
width: 400px;
}
\ No newline at end of file
......
$(function(){
function get2digits (num){
return ('0' + num).slice(-2);
}
function getDate(dateObj){
if(dateObj instanceof Date)
return dateObj.getFullYear() + '-' + get2digits(dateObj.getMonth()+1)+ '-' + get2digits(dateObj.getDate());
}
function getTime(dateObj){
if(dateObj instanceof Date)
return get2digits(dateObj.getHours()) + ':' + get2digits(dateObj.getMinutes())+ ':' + get2digits(dateObj.getSeconds());
}
function convertDate(){
$('[data-date]').each(function(index,element){
var dateString = $(element).data('date');
if(dateString){
var date = new Date(dateString);
$(element).html(getDate(date));
}
});
}
function convertDateTime(){
$('[data-date-time]').each(function(index,element){
var dateString = $(element).data('date-time');
if(dateString){
var date = new Date(dateString);
$(element).html(getDate(date)+' '+getTime(date));
}
});
}
convertDate();
convertDateTime();
});
\ No newline at end of file
const express = require('express');
const router = express.Router();
var express = require('express');
var router = express.Router();
var passport = require('passport');
require('../config/passport');
router.get('/', function(req,res){
res.render('home/welcome');
......@@ -9,4 +11,53 @@ router.get('/about', function(req,res){
res.render('home/about');
});
//Login
router.get('/login', function(req,res){
var username = req.flash('username')[0];
var errors = req.flash('errors')[0] || {};
res.render('home/login',{
username:username,
errors:errors
});
});
//Post login
router.post('/login',
function(req,res,next){
var errors = {};
var isValid = true;
if(!req.body.username){
isValid = false;
errors.username = 'Username is required!';
}
if(!req.body.password){
isValid = false;
errors.password = 'Password is required!';
}
if(isValid){
next();
}
else{
req.flash('errors',errors);
res.redirect('/login');
}
},
passport.authenticate('local-login', {
successRedirect : '/posts',
failureRedirect : '/login'
}
));
// Logout
router.get('/logout', function(req,res){
req.logout();
res.redirect('/');
});
module.exports = router;
\ No newline at end of file
......
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
var express = require('express');
var router = express.Router();
var Post = require('../models/Post');
// Post home
......
var express = require('express');
var router = express.Router();
var User = require('../models/User');
// Index // 1
router.get('/', function(req, res){
User.find({})
.sort({username:1})
.exec(function(err, users){
if(err) return res.json(err);
res.render('users/index', {users:users});
});
});
// New
router.get('/new', function(req, res){
var user = req.flash('user')[0] || {};
var errors = req.flash('errors')[0] || {};
res.render('users/new', {user:user, errors:errors});
});
// create
router.post('/', function(req, res){
User.create(req.body, function(err, user){
if(err){
req.flash('user', req.body);
req.flash('errors', parseError(err));
return res.redirect('/users/new');
}
res.redirect('/users');
});
});
// show
router.get('/:username', function(req, res){
User.findOne({username:req.params.username}, function(err, user){
if(err) return res.json(err);
res.render('users/show', {user:user});
});
});
// edit
router.get('/:username/edit', function(req, res){
var user = req.flash('user')[0];
var errors = req.flash('errors')[0] || {};
if(!user){
User.findOne({username:req.params.username}, function(err, user){
if(err) return res.json(err);
res.render('users/edit', {username:req.params.username, user:user, errors:errors});
});
}
else{
res.render('users/edit', {username:req.params.username, user:user, errors:errors });
}
});
// update
router.put('/:username', function(req, res, next){
User.findOne({username:req.params.username}) // 2-1
.select('password') // 2-2
.exec(function(err, user){
if(err) return res.json(err);
// update user object
user.originalPassword = user.password;
user.password = req.body.newPassword? req.body.newPassword : user.password; // 2-3
for(var p in req.body){ // 2-4
user[p] = req.body[p];
}
// save updated user
user.save(function(err, user){
if(err){
req.flash('user', req.body);
req.flash('errors', parseError(err));
return res.redirect('/users/'+req.params.username+'/edit');
}
res.redirect('/users/'+user.username);
});
});
});
// destroy
router.delete('/:username', function(req, res){
User.deleteOne({username:req.params.username}, function(err){
if(err) return res.json(err);
res.redirect('/users');
});
});
module.exports = router;
function parseError(errors){
var parsed = {};
if(errors.name == 'ValidationError'){
for(var name in errors.errors){
var validationError = errors.errors[name];
parsed[name] = {message:validationError.message};
}
}
else if(errors.code == '11000' && errors.errmsg.indexOf('username') > 0){
parsed.username = {message:'Already exists!'};
}
else {
parsed.unhandled = JSON.stringify(errors);
}
return parsed;
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container">
<h3 class="mb-3">Login</h3>
<form class="user-form" action="/login" method="post">
<div class="form-group row">
<label for="username" class="col-sm-3 col-form-label">Username</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" value="<%= username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>">
<% if(errors.username){ %>
<span class="invalid-feedback"><%= errors.username %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-3 col-form-label">Password</label>
<div class="col-sm-9">
<input type="password" id="password" name="password" value="" class="form-control <%= (errors.password)?'is-invalid':'' %>">
<% if(errors.password){ %>
<span class="invalid-feedback"><%= errors.password %></span>
<% } %>
</div>
</div>
<% if(errors.login){ %>
<div class="invalid-feedback d-block"><%= errors.login %></div>
<% } %>
<div class="mt-3">
<input class="btn btn-primary" type="submit" value="Submit">
</div>
</form>
</div>
</body>
</html>
\ No newline at end of file
......@@ -13,4 +13,8 @@
<!-- my css -->
<link rel="stylesheet" href="/css/master.css">
<!-- my js -->
<script src="/js/script.js"></script>
<title>Mapmory</title>
\ No newline at end of file
......
......@@ -8,6 +8,17 @@
<ul class="navbar-nav">
<li class="nav-item"><a href="/" class="nav-link">Home</a></li>
<li class="nav-item"><a href="/about" class="nav-link">About</a></li>
<li class="nav-item"><a href="/posts" class="nav-link">Posts</a></li>
</ul>
<ul class="navbar-nav ml-auto"> <!-- 우측 정렬 -->
<% if(isAuthenticated){ %>
<li class="nav-item"><a href="/users/<%= currentUser.username %>" class="nav-link">My Account</a></li>
<li class="nav-item"><a href="/logout" class="nav-link">Logout</a></li>
<% } else { %>
<li class="nav-item"><a href="/users/new" class="nav-link">Sign Up</a></li>
<li class="nav-item"><a href="/login" class="nav-link">Login</a></li>
<% } %>
</ul>
</div>
</div>
......
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb p-1 pl-2 pr-2">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/posts">Board</a></li>
<li class="breadcrumb-item"><a href="/posts/<%= post._id %>"><%= post.title %></a></li>
<li class="breadcrumb-item active" aria-current="page">Edit Post</li>
</ol>
</nav>
<form action="/posts/<%= post._id %>?_method=put" method="post">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" name="title" value="<%= post.title %>" class="form-control">
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea id="body" name="body" rows="5" class="form-control"><%= post.body %></textarea>
</div>
<div>
<a class="btn btn-primary" href="/posts/<%= post._id %>">Back</a>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</body>
</html>
\ No newline at end of file
......
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<h2 class="mb-3">Board</h2>
<table class="board-table table table-sm border-bottom">
<thead class="thead-light">
<tr>
<th scope="col">Title</th>
<th scope="col" class="date">Date</th>
</tr>
</thead>
<tbody>
<% if(posts == null || posts.length == 0){ %>
<tr>
<td colspan=2> EMPTY </td>
</tr>
<% } %>
<% posts.forEach(function(post) { %>
<tr>
<td>
<a href="/posts/<%= post._id %>"><div class="ellipsis"><%= post.title %></div></a>
</td>
<td class="date">
<span data-date="<%= post.createdAt %>"></span>
</td>
</tr>
<% }) %>
</tbody>
</table>
<div>
<a class="btn btn-primary" href="/posts/new">New</a>
</div>
</div>
</body>
</html>
\ No newline at end of file
......
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<nav aria-label="breadcrumb"> <!-- 1 -->
<ol class="breadcrumb p-1 pl-2 pr-2">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/posts">Board</a></li>
<li class="breadcrumb-item active" aria-current="page">New Post</li>
</ol>
</nav>
<form action="/posts" method="post">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" name="title" value="" class="form-control">
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea id="body" name="body" rows="5" class="form-control"></textarea>
</div>
<div>
<a class="btn btn-primary" href="/posts">Back</a>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</body>
</html>
\ No newline at end of file
......
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<nav aria-label="breadcrumb">
<ol class="breadcrumb p-1 pl-2 pr-2">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="/posts">Board</a></li>
<li class="breadcrumb-item active" aria-current="page"><%= post.title %></li>
</ol>
</nav>
<div class="card">
<h5 class="card-header p-2"><%= post.title %></h5>
<div class="row"> <!-- 1 -->
<div class="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1"> <!-- 1 -->
<div class="post-body p-2"><%= post.body %></div>
</div>
<div class="col-md-5 col-lg-4 col-xl-3 order-sm-1 order-md-2"> <!-- 1 -->
<div class="post-info card m-2 p-2">
<div><span>Created</span> : <span data-date-time="<%= post.createdAt %>"></span></div> <!-- 2 -->
<% if(post.updatedAt) { %>
<div><span>Updated</span> : <span data-date-time="<%= post.updatedAt %>"></span></div> <!-- 2 -->
<% } %>
</div>
</div>
</div>
</div>
<div class="mt-3">
<a class="btn btn-primary" href="/posts">Back</a>
<a class="btn btn-primary" href="/posts/<%= post._id %>/edit">Edit</a>
<form action="/posts/<%= post._id %>?_method=delete" method="post" class="d-inline">
<a class="btn btn-primary" href="javascript:void(0)" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a>
</form>
</div>
</div>
</body>
</html>
\ No newline at end of file
......
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<h3 class="mb-3">Edit User</h3>
<form action="/users/<%= username %>?_method=put" method="post"> <!-- 1 -->
<div class="form-group row">
<label for="currentPassword" class="col-sm-3 col-form-label">Current Password*</label>
<div class="col-sm-9 col-sm-offset-3">
<input type="password" id="currentPassword" name="currentPassword" value="" class="form-control <%= (errors.currentPassword)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.currentPassword){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.currentPassword.message %></span>
<% } %>
</div>
</div>
<hr></hr>
<div class="form-group row">
<label for="username" class="col-sm-3 col-form-label">Username*</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" value="<%= user.username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.username){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.username.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Name*</label>
<div class="col-sm-9">
<input type="text" id="name" name="name" value="<%= user.name %>" class="form-control <%= (errors.name)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.name){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.name.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="email" class="col-sm-3 col-form-label">Email</label>
<div class="col-sm-9">
<input type="text" id="email" name="email" value="<%= user.email %>" class="form-control <%= (errors.email)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.email){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.email.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="newPassword" class="col-sm-3 col-form-label">New Password</label>
<div class="col-sm-9 col-sm-offset-3">
<input type="password" id="newPassword" name="newPassword" value="" class="form-control <%= (errors.newPassword)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.newPassword){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.newPassword.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="passwordConfirmation" class="col-sm-3 col-form-label">Password Confirmation</label>
<div class="col-sm-9 col-sm-offset-3">
<input type="password" id="passwordConfirmation" name="passwordConfirmation" value="" class="form-control <%= (errors.passwordConfirmation)?'is-invalid':'' %>"> <!-- 2 -->
<% if(errors.passwordConfirmation){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.passwordConfirmation.message %></span>
<% } %>
</div>
</div>
<p>
<small>*Required</small>
</p>
<% if(errors.unhandled){ %> <!-- 4 -->
<div class="alert alert-danger">
<%= errors.unhandled %>
</div>
<% } %>
</form>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<h3 class="mb-3">Users</h3>
<ul class="list-group">
<% if(users == null || users.length == 0){ %>
<li class="list-group-item"> There is no user yet.</li>
<% } %>
<% users.forEach(function(user) { %>
<li class="list-group-item">
<a href="/users/<%= user.username %>"><%= user.username %></a>
</li>
<% }) %>
</ul>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<h3 class="contentBoxTop mb-3">New User</h3>
<form action="/users" method="post">
<div class="form-group row">
<label for="username" class="col-sm-3 col-form-label">Username*</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" value="<%= user.username %>" class="form-control <%= (errors.username)?'is-invalid':'' %>"> <!-- 1, 2 -->
<% if(errors.username){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.username.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Name*</label>
<div class="col-sm-9">
<input type="text" id="name" name="name" value="<%= user.name %>" class="form-control <%= (errors.name)?'is-invalid':'' %>"> <!-- 1, 2 -->
<% if(errors.name){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.name.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="email" class="col-sm-3 col-form-label">Email</label>
<div class="col-sm-9">
<input type="text" id="email" name="email" value="<%= user.email %>" class="form-control <%= (errors.email)?'is-invalid':'' %>"> <!-- 1, 2 -->
<% if(errors.email){ %>
<span class="invalid-feedback"><%= errors.email.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-3 col-form-label">Password*</label>
<div class="col-sm-9">
<input type="password" id="password" name="password" value="" class="form-control <%= (errors.password)?'is-invalid':'' %>"> <!-- 1, 2 -->
<% if(errors.password){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.password.message %></span>
<% } %>
</div>
</div>
<div class="form-group row">
<label for="passwordConfirmation" class="col-sm-3 col-form-label">Password Confirmation*</label>
<div class="col-sm-9 col-sm-offset-3">
<input type="password" id="passwordConfirmation" name="passwordConfirmation" value="" class="form-control <%= (errors.passwordConfirmation)?'is-invalid':'' %>"> <!-- 1, 2 -->
<% if(errors.passwordConfirmation){ %> <!-- 3 -->
<span class="invalid-feedback"><%= errors.passwordConfirmation.message %></span>
<% } %>
</div>
</div>
<p>
<small>*Required</small>
</p>
<% if(errors.unhandled){ %> <!-- 4 -->
<div class="alert alert-danger">
<%= errors.unhandled %>
</div>
<% } %>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<%- include('../partials/head') %>
</head>
<body>
<%- include('../partials/nav') %>
<div class="container mb-3">
<h3 class="contentBoxTop"><%= user.username %></h3>
<form class="user-form" action="/users" method="post">
<fieldset disabled>
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Name</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="name" name="name" value="<%= user.name %>">
</div>
</div>
<div class="form-group row">
<label for="email" class="col-sm-3 col-form-label">Email</label>
<div class="col-sm-9">
<input class="form-control" type="text" id="email" name="email" value="<%= user.email %>">
</div>
</div>
</fieldset>
</form>
<div>
<a class="btn btn-primary" href="/users">Back</a>
<a class="btn btn-primary" href="/users/<%= user.username %>/edit">Edit</a>
<form action="/users/<%= user.username %>?_method=delete" method="post" class="d-inline">
<a class="btn btn-primary" href="javascript:void(0)" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a>
</form>
</div>
</div>
</body>
</html>
\ No newline at end of file