Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
IMGUR_CLIENT_ID=
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: NODE_ENV=production node app.js
34 changes: 34 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
const path = require('path') // node.js 的原生模組
const express = require('express')
const routes = require('./routes')
// 如果應用程式不是在「正式上線模式 (production mode)」中執行,就透過 dotenv 去讀取在 env 檔案裡的資訊
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}

const handlebars = require('express-handlebars') // 引入 express-handlebars
const methodOverride = require('method-override')
const flash = require('connect-flash')
const session = require('express-session')
const passport = require('./config/passport')

const { getUser } = require('./helpers/auth-helpers')
const handlebarsHelpers = require('./helpers/handlebars-helpers') // 引入 handlebars-helpers
const app = express()
const port = process.env.PORT || 3000

const SESSION_SECRET = 'secret'
// 註冊 Handlebars 樣板引擎,並指定副檔名為 .hbs
app.engine('hbs', handlebars({ extname: '.hbs', helpers: handlebarsHelpers }))
// 設定使用 Handlebars 做為樣板引擎
app.set('view engine', 'hbs')

app.use(express.urlencoded({ extended: true })) // body-parser套件
app.use(
session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false })
)
app.use(passport.initialize()) // 初始化 Passport
app.use(passport.session()) // 增啟動 session 功能
app.use(flash()) // 掛載套件
app.use(methodOverride('_method'))
app.use('/upload', express.static(path.join(__dirname, 'upload')))
app.use((req, res, next) => {
res.locals.success_messages = req.flash('success_messages') // 設定 success_msg 訊息
res.locals.error_messages = req.flash('error_messages') // 設定 warning_msg 訊息
res.locals.user = getUser(req)
next()
})
app.use(routes)

app.listen(port, () => {
Expand Down
6 changes: 1 addition & 5 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
"use_env_variable": "MYSQL_DATABASE_URL"
},
"travis": {
"username": "travis",
Expand Down
50 changes: 50 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const passport = require('passport')
const LocalStrategy = require('passport-local')
const bcrypt = require('bcryptjs')
const db = require('../models')
const User = db.User
// set up Passport strategy
passport.use(
new LocalStrategy(
// customize user field
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
// authenticate user
(req, email, password, cb) => {
User.findOne({ where: { email } }).then(user => {
if (!user) {
return cb(
null,
false,
req.flash('error_messages', '帳號或密碼輸入錯誤!')
)
}
bcrypt.compare(password, user.password).then(res => {
if (!res) {
return cb(
null,
false,
req.flash('error_messages', '帳號或密碼輸入錯誤!')
)
}
return cb(null, user)
})
})
}
)
)
// serialize and deserialize user
passport.serializeUser((user, cb) => {
cb(null, user.id)
})
passport.deserializeUser((id, cb) => {
User.findByPk(id).then(user => {
user = user.toJSON()
console.log(user) // 暫時添加
return cb(null, user)
})
})
module.exports = passport
173 changes: 173 additions & 0 deletions controllers/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
const { Restaurant, User, Category } = require('../models')
const { imgurFileHandler } = require('../helpers/file-helpers') // 將 file-helper 載進來

const adminController = {
getRestaurants: (req, res, next) => {
Restaurant.findAll({
raw: true,
nest: true, // 使資料排料整齊
include: [Category]
// 如果想使用關聯資料時,需要透過include將資料拉進來,這樣值才會在findAll的回傳值中
})

.then(restaurants => res.render('admin/restaurants', { restaurants }))

.catch(err => next(err))
},
createRestaurant: (req, res, next) => {
return Category.findAll({
raw: true
})
.then(categories => res.render('admin/create-restaurant', { categories }))
.catch(err => next(err))
},
postRestaurant: (req, res, next) => {
const { name, tel, address, openingHours, description, categoryId } =
req.body

if (!name) throw new Error('Name為必填欄位')

const { file } = req // 把檔案從req從取出來

imgurFileHandler(file) // 將取出的檔案傳給 file-helper (localFileHandler)處理後
.then(filePath =>
Restaurant.create({
name,
tel,
address,
openingHours,
description,
image: filePath || null,
categoryId
})
)

.then(() => {
req.flash('success_messages', 'restaurant was successfully created')

res.redirect('/admin/restaurants')
})
.catch(err => next(err))
},
getRestaurant: (req, res, next) => {
Restaurant.findByPk(req.params.id, {
raw: true,
nest: true, // 使資料排料整齊
include: [Category]
// 如果想使用關聯資料時,需要透過include將資料拉進來,這樣值才會在findAll的回傳值中
})

.then(restaurant => {
if (!restaurant) throw new Error('此restaurant不存在')

res.render('admin/restaurant', { restaurant })
})

.catch(err => next(err))
},
editRestaurant: (req, res, next) => {
return Promise.all([
Restaurant.findByPk(req.params.id, {
raw: true
}),
Category.findAll({
raw: true
})
])

.then(([restaurant, categories]) => {
if (!restaurant) throw new Error('此restaurant不存在')

res.render('admin/edit-restaurant', { restaurant, categories })
})
.catch(err => next(err))
},
putRestaurant: (req, res, next) => {
const { name, tel, address, openingHours, description, categoryId } =
req.body

if (!name) throw new Error('Name為必填欄位')

const { file } = req // 把檔案取出來

Promise.all([
// 非同步處理
Restaurant.findByPk(req.params.id), // 去資料庫查有沒有這間餐廳
imgurFileHandler(file) // 把檔案傳到 file-helper 處理
])
.then(([restaurant, filePath]) => {
if (!restaurant) throw new Error('此restaurant不存在')

return restaurant.update({
name,
tel,
address,
openingHours,
description,
image: filePath || restaurant.image, // 如果 filePath 是 Truthy (使用者有上傳新照片) 就用 filePath,是 Falsy (使用者沒有上傳新照片) 就沿用原本資料庫內的值
categoryId
})
})
.then(() => {
req.flash('success_messages', 'restaurant was successfully to update')
res.redirect('/admin/restaurants')
})
.catch(err => next(err))
},
deleteRestaurant: (req, res, next) => {
return Restaurant.findByPk(req.params.id)
.then(restaurant => {
if (!restaurant) throw new Error('此restaurant不存在')

return restaurant.destroy()
})
.then(() => res.redirect('/admin/restaurants'))
.catch(err => next(err))
},
getUsers: (req, res, next) => {
return User.findAll({
raw: true
})
.then(users => {
// 判斷is_admin為admin或user,添加了role屬性
// 使用map一一檢視並增加role屬性
const userRole = users.map(user => {
return {
id: user.id,
email: user.email,
name: user.name,
isAdmin: user.isAdmin,
role: user.isAdmin ? 'admin' : 'user'
}
})
res.render('admin/users', { users: userRole })
})
.catch(err => next(err))
},
patchUser: (req, res, next) => {
return User.findByPk(req.params.id)
.then(user => {
if (!user) {
throw new Error('此user不存在')
}

if (user.email === 'root@example.com') {
req.flash('error_messages', '禁止變更 root 權限')
return res.redirect('back')
}

return user.update({
isAdmin: !user.isAdmin
})
})

.then(() => {
req.flash('success_messages', '使用者權限變更成功')
res.redirect('/admin/users')
})

.catch(err => next(err))
}
}

module.exports = adminController
47 changes: 47 additions & 0 deletions controllers/category-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { Category } = require('../models')

const categoryController = {
getCategories: (req, res, next) => {
return Promise.all([
Category.findAll({ raw: true }),
req.params.id ? Category.findByPk(req.params.id, { raw: true }) : null
// 也就是說,當req.params.id存在,那我控制器便從資料庫尋找該id的資料
])
.then(([categories, category]) =>
// category便是該id的資料,最後回傳至前端
res.render('admin/categories', { categories, category })
)
.catch(err => next(err))
},
postCategory: (req, res, next) => {
const { name } = req.body

if (!name) throw new Error('Category name is required!')

return Category.create({ name })
.then(() => res.redirect('/admin/categories'))
.catch(err => next(err))
},
putCategory: (req, res, next) => {
const { name } = req.body
if (!name) throw new Error('Category name is required!')
return Category.findByPk(req.params.id)
.then(category => {
if (!category) throw new Error("Category doesn't exist!")
return category.update({ name })
})
.then(() => res.redirect('/admin/categories'))
.catch(err => next(err))
},
deleteCategory: (req, res, next) => {
return Category.findByPk(req.params.id)
.then(category => {
if (!category) throw new Error("Category doesn't exist!")
return category.destroy()
})
.then(() => res.redirect('/admin/categories'))
.catch(err => next(err))
}
}

module.exports = categoryController
63 changes: 63 additions & 0 deletions controllers/restaurant-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 負責處理前台餐廳相關的請求 (request)
const { Restaurant, Category } = require('../models')

const restaurantController = {
// restaurantController是一個物件
// getRestaurants負責處理瀏覽餐廳頁面的函式,主要為render restaurants 的樣板
getRestaurants: (req, res) => {
return Restaurant.findAll({
raw: true,
nest: true,
include: [Category]
}).then(restaurants => {
const data = restaurants.map(r => ({
...r,
description: r.description.substring(0, 50)
}))

return res.render('restaurants', { restaurants: data })
})
},
getRestaurant: (req, res, next) => {
return Restaurant.findByPk(req.params.id, {
include: [Category]
})
.then(restaurant => {
if (!restaurant) throw new Error('此restaurant不存在')

return restaurant.increment('viewCounts')
})

.then(() => {
return Restaurant.findByPk(req.params.id, {
include: [Category]
})
})

.then(updateRestaurant => {
res.render('restaurant', {
restaurant: updateRestaurant.toJSON()
})
})

.catch(err => next(err))
},
getDashboard: (req, res, next) => {
return Restaurant.findByPk(req.params.id, {
raw: true,
nest: true,
include: [Category]
})

.then(restaurant => {
if (!restaurant) throw new Error('無法獲取更新後的restaurant')

res.render('dashboard', {
restaurant
})
})
.catch(err => next(err))
}
}

module.exports = restaurantController
Loading