Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
574fda4
feat: add handlebars
TheyCallMeAndyBro Oct 31, 2023
125c956
add index page
TheyCallMeAndyBro Oct 31, 2023
c7de481
add admin index page
TheyCallMeAndyBro Oct 31, 2023
deba3e6
add user model
TheyCallMeAndyBro Oct 31, 2023
c50b104
user signup
TheyCallMeAndyBro Oct 31, 2023
3d5fd9f
flash msg & signup verification
TheyCallMeAndyBro Oct 31, 2023
7e3444f
passpot iniy & signin
TheyCallMeAndyBro Nov 1, 2023
867893f
add header & footer
TheyCallMeAndyBro Nov 1, 2023
f2ebe7b
user model add is_admin
TheyCallMeAndyBro Nov 1, 2023
43ca85f
add restaurant model
TheyCallMeAndyBro Nov 1, 2023
a9d66b7
modify admin restaurant page
TheyCallMeAndyBro Nov 1, 2023
9b8b299
create restaurant
TheyCallMeAndyBro Nov 2, 2023
a11c5d6
admin restaurant page
TheyCallMeAndyBro Nov 2, 2023
de3d6aa
admin update restaurant
TheyCallMeAndyBro Nov 2, 2023
3400878
delete restaurant
TheyCallMeAndyBro Nov 2, 2023
f9579e4
add restaurant image
TheyCallMeAndyBro Nov 2, 2023
9d58e41
add seed data
TheyCallMeAndyBro Nov 2, 2023
5103304
add users page and patch function
TheyCallMeAndyBro Nov 3, 2023
d36caa7
change success/error _messages
TheyCallMeAndyBro Nov 3, 2023
13fb740
fix file
TheyCallMeAndyBro Nov 3, 2023
c4768ce
change admin restaurants/users view
TheyCallMeAndyBro Nov 6, 2023
f3ea249
add category model
TheyCallMeAndyBro Nov 6, 2023
83b8862
update seed files
TheyCallMeAndyBro Nov 6, 2023
5fd9ec7
show category on admin restaurant page
TheyCallMeAndyBro Nov 6, 2023
b449e54
add categories selector on admin create and edit page
TheyCallMeAndyBro Nov 6, 2023
4442247
add ifClond hbs & update category selector
TheyCallMeAndyBro Nov 6, 2023
8bd610d
add admin categories page
TheyCallMeAndyBro Nov 7, 2023
751bcb3
category create
TheyCallMeAndyBro Nov 7, 2023
19595d7
category update
TheyCallMeAndyBro Nov 7, 2023
b6c7dcb
category delete
TheyCallMeAndyBro Nov 7, 2023
d6900fe
add restaurants index page
TheyCallMeAndyBro Nov 7, 2023
d4d8c47
add restaurant page
TheyCallMeAndyBro Nov 7, 2023
d3e23c2
add viewCounts function by increment
TheyCallMeAndyBro Nov 7, 2023
38ee3ab
fix conflict
TheyCallMeAndyBro Nov 7, 2023
175c1ef
add categories navbar on restaurants index page
TheyCallMeAndyBro Nov 7, 2023
cddf9ad
add pagination on restaurants index page
TheyCallMeAndyBro Nov 8, 2023
af25fcd
add comment model
TheyCallMeAndyBro Nov 8, 2023
091a6cf
add post comment on restaurant page
TheyCallMeAndyBro Nov 8, 2023
08c29b2
show comments on restaurant page
TheyCallMeAndyBro Nov 8, 2023
dd7aa03
update restaurant comment time/sorting
TheyCallMeAndyBro Nov 8, 2023
7660760
add delete restaurant comment for admin
TheyCallMeAndyBro Nov 8, 2023
48b2d60
Q1 done
TheyCallMeAndyBro Nov 8, 2023
dbd7e35
R03 Q2 done
TheyCallMeAndyBro Nov 8, 2023
32d20bb
feat: add R01.test.js and unit-test-helpers.js
Carrot7712 Jan 13, 2022
38e9906
feat:add R02.test.js
Carrot7712 Jan 13, 2022
04584a6
add R03.test.js file
AmberYen Mar 2, 2022
df993fe
correcting typo in comment
zjzheng17 Jul 18, 2022
5302208
Update unit-test-helper.js update()
tuterwell Sep 12, 2023
47c7859
test: fix update method
eugenechen0514 Sep 17, 2023
f12a97e
R03 Q2 done
TheyCallMeAndyBro Nov 8, 2023
b356559
finished R03 test
TheyCallMeAndyBro Nov 8, 2023
7834514
merge
TheyCallMeAndyBro Nov 8, 2023
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
28 changes: 27 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
const path = require('path')
const express = require('express')
const handlebars = require('express-handlebars')
const flash = require('connect-flash')
const session = require('express-session')
const methodOverride = require('method-override')
const passport = require('./config/passport')

const routes = require('./routes')
const { generalPartialsHandler } = require('./middlewares/partials-handler')
const { generalErrorHandler } = require('./middlewares/error-handler')
const handlebarsHelper = require('./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: handlebarsHelper }))
// 設定使用 Handlebars 做為樣板引擎
app.set('view engine', 'hbs')
app.set('views', './views')

app.use(routes)
app.use(express.urlencoded({ extended: true }))
app.use(session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false }))
app.use(flash()) // flash這種功能存在 session裡
app.use(methodOverride('_method')) // _method為自定義
app.use('/upload', express.static(path.join(__dirname, 'upload'))) // 以/upload開頭的才被允許訪問 而__dirname/upload 被設為根目錄

app.use(passport.initialize())
app.use(passport.session())
app.use(generalPartialsHandler)

app.use(routes)
app.use(generalErrorHandler)
app.listen(port, () => {
console.info(`Example app listening on port ${port}!`)
})
Expand Down
2 changes: 1 addition & 1 deletion config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@
"dialect": "mysql",
"logging": false
}
}
}
36 changes: 36 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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', '帳號或密碼輸入錯誤!'))
bcrypt.compare(password, user.password).then(res => {
if (!res) return cb(null, false, req.flash('error', '帳號或密碼輸入錯誤!'))
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()
return cb(null, user)
})
})
module.exports = passport
126 changes: 126 additions & 0 deletions controllers/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
const { Restaurant, User, Category } = require('../models')
const { localFileHandler } = require('../helpers/file-helpers')

const adminController = {
getRestaurants: (req, res, next) => {
return Restaurant.findAll({
raw: true,
nest: true, // 讓他從 Restaurant[Category.id] => Restaurant.Category.id 比較好理解
include: [Category] // 包含Category的資料
})
.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('Restaurant name is required !')

const { file } = req // 這邊要獲取的是req中file這個檔案 而req.body.file 獲取的是 name=file 輸入的字串

localFileHandler(file) // 傳入req獲取的file 有無檔案邏輯已在helpers內做完
.then(filePath => { // filePath為最後resolve的`/${fileName}`路徑字符串
return 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) => {
return Restaurant.findByPk(req.params.id, {
raw: true,
nest: true,
include: [Category]
})
.then(restaurant => {
if (!restaurant) throw new Error('Restaurant did not exist!')
res.render('admin/restaurant', { restaurant })
// 在只有單筆資料時候 可以使用 { restaurant: restaurant.toJSON() } 取代 raw: true, nest: true,
})
.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 did not exist!')

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('Restaurant name is required !')

const { file } = req // 這邊要獲取的是req中file這個檔案 而req.body.file 獲取的是 name=file 輸入的字串

// 傳入req獲取的file 有無檔案邏輯已在helpers內做完
// Promise.all([a.b]) Promise.all會把參數a,b做完 .then會收到a.b的結果 在執行 .then([a,b])
return Promise.all([
Restaurant.findByPk(req.params.id),
localFileHandler(file)
])
.then(([restaurant, filePath]) => {
if (!restaurant) throw new Error('Restaurant did not exist!')
// 也可用Restaurant.update寫法 裡面多添加where 就好
// image: filePath || restaurant.image 如果有編輯照片 就用新的 沒有用舊的
return restaurant.update({ name, tel, address, openingHours, description, image: filePath || restaurant.image, categoryId })
})

.then(() => {
req.flash('success_messages', 'Update successfully!')
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 did not exist!')
// 也可用Restaurant.update寫法 裡面多添加where 就好
return restaurant.destroy()
})
.then(() => {
req.flash('success_messages', 'Delete successfully!')
res.redirect('/admin/restaurants')
})
.catch(err => next(err))
},
getUsers: (req, res) => {
return User.findAll({
raw: true
})
.then(users => res.render('admin/users', { users }))
},
patchUser: (req, res, next) => {
return User.findByPk(req.params.id)
.then(user => {
if (!user) throw new Error('User did not exist!')
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', '使用者權限變更成功')
return res.redirect('/admin/users')
})
.catch(err => next(err))
}
}
module.exports = adminController
50 changes: 50 additions & 0 deletions controllers/category-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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
])
.then(([categories, category]) => {
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 didn't exist!") // 反查,確認要刪除的類別存在,再進行下面刪除動作
return category.destroy()
})
.then(() => res.redirect('/admin/categories'))
.catch(err => next(err))
}
}

module.exports = categoryController
40 changes: 40 additions & 0 deletions controllers/comment-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { Comment, User, Restaurant } = require('../models')
const commentController = {

postComment: (req, res, next) => {
const { restaurantId, text } = req.body
const userId = req.user.id

if (!text) throw new Error('Comment text is required!')

return Promise.all([
User.findByPk(userId),
Restaurant.findByPk(restaurantId)
])
.then(([user, restaurant]) => {
if (!user) throw new Error("User didn't exist!")
if (!restaurant) throw new Error("Restaurant didn't exist!")

return Comment.create({
text,
restaurantId,
userId
})
})
.then(() => {
res.redirect(`/restaurants/${restaurantId}`)
})
.catch(err => next(err))
},
deleteComment: (req, res, next) => {
return Comment.findByPk(req.params.id)
.then(comment => {
if (!comment) throw new Error("Comment didn't exist!")

return comment.destroy()
})
.then(deletedComment => res.redirect(`/restaurants/${deletedComment.restaurantId}`))
.catch(err => next(err))
}
}
module.exports = commentController
70 changes: 70 additions & 0 deletions controllers/restaurant-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 用物件的方式儲存
const { Restaurant, Category, Comment, User } = require('../models')
const { getOffset, getPagination } = require('../helpers/pagination-helpers')

const restaurantController = {
getRestaurants: (req, res) => {
const DEFAULT_LIMIT = 9

const categoryId = Number(req.query.categoryId) || '' // 變成數字是因為 id在db資料庫值為integer

const page = Number(req.query.page) || 1
const limit = Number(req.query.limit) || DEFAULT_LIMIT
const offset = getOffset(limit, page)

return Promise.all([
Restaurant.findAndCountAll({
include: Category,
where: {
...(categoryId ? { categoryId } : {})
},
limit,
offset,
nest: true,
raw: true
}),
Category.findAll({ raw: true })
])
.then(([restaurants, categories]) => {
const data = restaurants.rows.map(r => ({
...r,
description: r.description.substring(0, 50) // 修改...r的description 縮減資料到50個字
}))
return res.render('restaurants', {
restaurants: data,
categories,
categoryId,
pagination: getPagination(limit, page, restaurants.count)
})
})
},
getRestaurant: (req, res, next) => {
return Restaurant.findByPk(req.params.id, {
include: [
Category,
{ model: Comment, include: User }
// 先找 Category 再找 Comment 再利用 Comment 關係找 User 使用User資料就變成Restaurant.Comment.User.id
],
order: [[Comment, 'createdAt', 'DESC']]
})
.then(restaurant => {
if (!restaurant) throw new Error("Restaurant didn't exist!")
restaurant.increment('viewCounts', { by: 1 })
res.render('restaurant', { restaurant: restaurant.toJSON() })
})
.catch(err => next(err))
},
getDashboard: (req, res, next) => {
return Restaurant.findByPk(req.params.id, {
include: Category,
raw: true,
nest: true
})
.then(restaurant => {
if (!restaurant) throw new Error("Restaurant didn't exist!")
res.render('dashboard', { restaurant })
})
.catch(err => next(err))
}
}
module.exports = restaurantController
Loading