Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fca7f3b
feat: add R01.test.js and unit-test-helpers.js
Carrot7712 Jan 13, 2022
b54a2e9
add R02.test.js file
AmberYen Mar 2, 2022
9d566b6
correcting typo in comment
zjzheng17 Jul 18, 2022
d2ac558
feat:add handlebars
Peter-Huang888 Aug 6, 2023
8cac267
feat:add handlebars
Peter-Huang888 Aug 6, 2023
837c626
feat:add index page
Peter-Huang888 Aug 6, 2023
c94c5c9
feat:add admin index page
Peter-Huang888 Aug 6, 2023
4e2a39f
feat:add user model
Peter-Huang888 Aug 6, 2023
ef17dc1
fix: type error
Peter-Huang888 Aug 7, 2023
04bce24
feat:user signup
Peter-Huang888 Aug 7, 2023
329c688
feat: flash msg & signup verification
Peter-Huang888 Aug 7, 2023
efacd04
feat: passport init & signin
Peter-Huang888 Aug 8, 2023
c1bd594
feat: add header & footer
Peter-Huang888 Aug 8, 2023
97eb718
feat: user model add is_admin
Peter-Huang888 Aug 8, 2023
c58d93b
doc:add table name
Peter-Huang888 Aug 8, 2023
66a26cc
feat: add restaurant model
Peter-Huang888 Aug 11, 2023
0193aa9
feat: modify admin restaurants page
Peter-Huang888 Aug 11, 2023
1e8cc57
feat: create restaurant
Peter-Huang888 Aug 11, 2023
376aa49
feat: admin restaurant page
Peter-Huang888 Aug 14, 2023
44d7473
feat: admin update restaurant
Peter-Huang888 Aug 14, 2023
81f8491
feat: delete restaurant
Peter-Huang888 Aug 14, 2023
d70145f
feat: add restaurant image
Peter-Huang888 Aug 14, 2023
9291fef
feat: add seed data
Peter-Huang888 Aug 16, 2023
414b8f7
feat: modify config for heroku
Peter-Huang888 Aug 16, 2023
6d05cdf
feat:add imgur api
Peter-Huang888 Aug 23, 2023
87482db
Merge branch R01 into main
Peter-Huang888 Aug 31, 2023
e9cca1d
feat: add category model
Peter-Huang888 Sep 7, 2023
f75c75b
feat: update seed files
Peter-Huang888 Sep 7, 2023
6eb4b72
feat: show category on admin restaurant pages
Peter-Huang888 Sep 7, 2023
bc66dc3
feat:add categories selector on admin create and edit page
Peter-Huang888 Sep 7, 2023
10f2f55
feat: add ifCond hbs helper & update category selector
Peter-Huang888 Sep 7, 2023
383e171
feat:add admin categories page
Peter-Huang888 Sep 7, 2023
f2a0346
feat:category create
Peter-Huang888 Sep 7, 2023
65aac0f
feat:category update
Peter-Huang888 Sep 7, 2023
b41067b
feat:category delete
Peter-Huang888 Sep 7, 2023
1cef867
feat:add restaurants index page
Peter-Huang888 Sep 18, 2023
87f50cb
feat:add restaurant page
Peter-Huang888 Sep 18, 2023
1445463
Merge remote-tracking branch 'upstream/R02-test' into R02
Peter-Huang888 Sep 18, 2023
33e70fa
feat:add dashboard page and getDashboard function
Peter-Huang888 Sep 18, 2023
2990bd0
format code and add strong tag
Peter-Huang888 Sep 18, 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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
IMGUR_CLIENT_ID= 輸入你的 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
31 changes: 31 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
const path = require('path')
const express = require('express')
const exphbs = require('express-handlebars')
const flash = require('connect-flash')
const session = require('express-session')
const methodOverride = require('method-override')
const passport = require('./config/passport')

const { getUser } = require('./helpers/auth-helpers')
const handlebarsHelpers = require('./helpers/handlebars-helpers')

const routes = require('./routes')

if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}

const app = express()
const port = process.env.PORT || 3000
const SESSION_SECRET = 'secret'

app.engine('hbs', exphbs({ extname: '.hbs', helpers: handlebarsHelpers }))
app.set('view engine', 'hbs')

app.use(express.urlencoded({ extended: true }))
app.use(session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false }))
app.use(passport.initialize())
app.use(passport.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
8 changes: 2 additions & 6 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 All @@ -20,4 +16,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_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()
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 { imgurFileHandler } = require('../helpers/file-helpers')

const adminController = {
getRestaurants: (req, res, next) => {
Restaurant.findAll({
raw: true, // 取得plain object
nest: true,
include: [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 // 從 req.body 拿出表單裡的資料
if (!name) throw new Error('Restaurant name is required!') // name 是必填,若發現是空值就會終止程式碼,並在畫面顯示錯誤提示
const { file } = req // 把檔案取出來,也可以寫成 const file = req.file
imgurFileHandler(file) // 把取出的檔案傳給 file-helper 處理後
.then(filePath => Restaurant.create({ // 再產生一個新的 Restaurant 物件實例,並存入資料庫
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) => {
const id = req.params.id
Restaurant.findByPk(id, { // 去資料庫用id找一筆資料
raw: true, // 找到後整理格式再回傳
nest: true,
include: [Category]
})
.then(restaurant => {
if (!restaurant) { throw new Error("The restaurant didn't exist!") }
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 doesn't 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 // 把檔案取出來
Promise.all([ // 非同步處理
Restaurant.findByPk(req.params.id), // 去資料庫查有沒有這間餐廳
imgurFileHandler(file) // 把檔案傳到 file-helper 處理
])
.then(([restaurant, filePath]) => { // 以上兩樣事都做完以後
if (!restaurant) throw new Error("Restaurant didn't exist!")
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) => {
Restaurant.findByPk(req.params.id)
.then(restaurant => {
if (!restaurant) { throw new Error("The restaurant didn't exist!") }
return restaurant.destroy()
})
.then(() => res.redirect('admin/restaurants'))
.catch(err => next(err))
},
getUsers: (req, res, next) => {
return User.findAll({
raw: true // 取得plain object
})
.then(users => res.render('admin/users', { users }))
.catch(err => next(err))
},
patchUser: (req, res, next) => {
return User.findByPk(req.params.id)
.then(user => {
if (!user) throw new Error("The user doesn't 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', '使用者權限變更成功')
res.redirect('/admin/users')
})
.catch(err => next(err))
}
}
module.exports = adminController
71 changes: 71 additions & 0 deletions controllers/category-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { Restaurant, 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(() => {
req.flash('success_messages', 'category was successfully created')
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: async (req, res, next) => {
const deleteCategoryId = req.params.id
const uncategory = await Category.findOne({
raw: true,
where: { name: '未分類' }
})
const uncategoryId = uncategory.id
// 反查deleteCategory是否存在
try {
const deleteCategory = await Category.findByPk(deleteCategoryId, { raw: true })
if (!deleteCategory) { throw new Error("The category didn't exist!") }
if (deleteCategory.name === '未分類') { throw new Error("You can't delete this category!") }
} catch (err) {
next(err)
}
// 找出與deleteCategoryId相關的餐廳
try {
const restaurants = await Restaurant.findAll({ where: { categoryId: deleteCategoryId } })
if (restaurants) {
await Restaurant.update({ categoryId: uncategoryId }, { // 將categoryId變成未分類的ID
where: {
categoryId: deleteCategoryId
}
})
}
await Category.destroy({ where: { id: deleteCategoryId } })
} catch (err) {
next(err)
}
res.redirect('/admin/categories')
}
}

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

const restaurantController = {
getRestaurants: (req, res) => {
return Restaurant.findAll({
include: Category,
nest: true,
raw: true
}).then(restaurants => {
const data = restaurants.map(r => ({
...r,
description: r.description.substring(0, 50)
}))
return res.render('restaurants', { restaurants: data })
})
},
getRestaurant: (req, res, next) => {
const id = req.params.id
return Restaurant.findByPk(id, {
include: Category,
nest: true
})
.then(restaurant => {
if (!restaurant) { throw new Error("Restaurant didn't exist!") }
return restaurant.increment('viewCounts')
})
.then(updateRestaurant => res.render('restaurant', { restaurant: updateRestaurant.toJSON() }))
.catch(err => next(err))
},
getDashboard: (req, res, next) => {
const id = req.params.id
return Restaurant.findByPk(id, {
include: Category,
nest: true,
raw: true
})
.then(restaurant => {
if (!restaurant) { throw new Error("Restaurant didn't exist") }
res.render('dashboard', { restaurant })
})
.catch(err => next(err))
}
}
module.exports = restaurantController
44 changes: 44 additions & 0 deletions controllers/user-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const bcrypt = require('bcryptjs')
const db = require('../models')
const { User } = db

const userContorller = {
signUpPage: (req, res) => {
res.render('signup')
},
signUp: (req, res, next) => {
// 如果兩次輸入的密碼不同,就建立一個 Error 物件並拋出
if (req.body.password !== req.body.passwordCheck) throw new Error('Passwords do not match!')

// 確認資料裡面沒有一樣的 email,若有,就建立一個 Error 物件並拋出
User.findOne({ where: { email: req.body.email } })
.then(user => {
if (user) throw new Error('Email already exists!')
return bcrypt.hash(req.body.password, 10) // 前面加 return
})
.then(hash => User.create({ // 上面錯誤狀況都沒發生,就把使用者的資料寫入資料庫
name: req.body.name,
email: req.body.email,
password: hash
}))
.then(() => {
req.flash('success_messages', '成功註冊帳號!') // 並顯示成功訊息
res.redirect('/signin')
})
.catch(err => next(err)) // 接住前面拋出的錯誤,呼叫專門做錯誤處理的 middleware
},
signInPage: (req, res) => {
res.render('signin')
},
signIn: (req, res) => {
req.flash('success_messages', '成功登入!')
res.redirect('/restaurants')
},
logout: (req, res) => {
req.flash('success_messages', '登出成功!')
req.logout()
res.redirect('/signin')
}
}

module.exports = userContorller
Loading