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
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/node_modules/*
/tests/*
/tests/*
18 changes: 5 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
name: Node.js test

on:
pull_request_target:
push:
branches:
- main
# push:
# branches:
# - main
# - 'R*'
# - '*-test'
# pull_request:
# branches:
- '*-test'
pull_request:
branches:
# - main

env:
Expand Down Expand Up @@ -38,11 +34,7 @@ jobs:
# mysql user: 'github' # Required if "mysql root password" is empty, default is empty. The superuser for the specified database. Can use secrets, too
# mysql password: 'password' # Required if "mysql user" exists. The password for the "mysql user"
- run: mysql --version
- run: echo "checkout head ${{ github.event.pull_request.head.sha }}"
- run: echo "base ${{ github.event.pull_request.base.sha }}"
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand Down
34 changes: 32 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
const express = require('express')
const routes = require('./routes')

const handlebars = require('express-handlebars')
const app = express()
const port = process.env.PORT || 3000
const flash = require('connect-flash')
const session = require('express-session')
const SESSION_SECRET = 'secret'
const passport = require('./config/passport')
const { getUser } = require('./helpers/auth-helpers')
const handlebarsHelpers = require('./helpers/hbs-helpers')
const methodOverride = require('method-override')

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

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('upload'))
app.use((req, res, next) => {
res.locals.success_messages = req.flash('success_messages')
res.locals.error_messages = req.flash('error_messages')
res.locals.user = getUser(req)
next()
})
app.use(routes)

app.listen(port, () => {
console.info(`Example app listening on port ${port}!`)
console.info(`Example app listening on http://localhost:${port}`)
})

module.exports = app
51 changes: 51 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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)
})
}).catch(error => {
req.flash('error_messages', '登入失敗')
return cb(error)
})
}
)
)
// serialize and deserialize user
passport.serializeUser((user, cb) => {
cb(null, user.id)
})
passport.deserializeUser((id, cb) => {
User.findByPk(id, { raw: true }).then(user => {
cb(null, user)
})
})
module.exports = passport
127 changes: 127 additions & 0 deletions controllers/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const { Restaurant } = require('../models')
const { User } = require('../models')
const { localFileHandler } = require('../helpers/file-helpers')

const adminController = {
getRestaurants: (req, res, next) => {
Restaurant.findAll({
raw: true
})
.then(restaurants => res.render('admin/restaurants', { restaurants }))
.catch(err => next(err))
},
createRestaurant: (req, res) => {
return res.render('admin/create-restaurant')
},
postRestaurant: (req, res, next) => {
const { name, tel, address, openingHours, description } = req.body // 從 req.body 拿出表單裡的資料
if (!name) throw new Error('Restaurant name is required!') // name 是必填,若發先是空值就會終止程式碼,並在畫面顯示錯誤提示
const { file } = req
localFileHandler(file)
.then(filePath =>
Restaurant.create({
// 產生一個新的 Restaurant 物件實例,並存入資料庫
name,
tel,
address,
openingHours,
description,
image: filePath || null
})
)
.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, {
// 去資料庫用 id 找一筆資料
raw: true // 找到以後整理格式再回傳
})
.then(restaurant => {
if (!restaurant) throw new Error("Restaurant didn't exist!") // 如果找不到,回傳錯誤訊息,後面不執行
res.render('admin/restaurant', { restaurant })
})
.catch(err => next(err))
},
editRestaurant: (req, res, next) => {
// 新增這段
Restaurant.findByPk(req.params.id, {
raw: true
})
.then(restaurant => {
if (!restaurant) throw new Error("Restaurant didn't exist!")
res.render('admin/edit-restaurant', { restaurant })
})
.catch(err => next(err))
},
putRestaurant: (req, res, next) => {
const { name, tel, address, openingHours, description } = req.body
if (!name) throw new Error('Restaurant name is required!')
const { file } = req
Promise.all([Restaurant.findByPk(req.params.id), localFileHandler(file)])
.then(([restaurant, filePath]) => {
if (!restaurant) throw new Error("Restaurant didn't exist!")
return restaurant.update({
name,
tel,
address,
openingHours,
description,
image: filePath || restaurant.image
})
})
.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 didn't exist!")
return restaurant.destroy()
})
.then(() => res.redirect('/admin/restaurants'))
.catch(err => next(err))
},
getUsers: (req, res, next) => {
return User.findAll({
raw: true
})
.then(users => {
return 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.dataValues.email === 'root@example.com') {
req.flash('error_messages', '禁止變更 root 權限')
return res.redirect('back')
}
if (user.dataValues.isAdmin) {
return user.update({
isAdmin: false
})
} else {
return user.update({
isAdmin: true
})
}
})
.then(() => {
req.flash('success_messages', '使用者權限變更成功')
return res.redirect('/admin/users')
})

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

module.exports = adminController
7 changes: 7 additions & 0 deletions controllers/restaurant-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const restaurantController = {
getRestaurant: (req, res) => {
return res.render('restaurants')
}
}

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.User

const userController = {
signUpPage: (req, res) => {
res.render('signup')
},
signUp: (req, res, next) => {
const { name, email, password, passwordCheck } = req.body
if (password !== passwordCheck) throw new Error('Password do not match!')
User.findOne({ where: { email } })
.then(user => {
if (user) throw new Error('Email already exists!')
return bcrypt.hash(password, 10)
})
.then(hash =>
User.create({
name,
email,
password: hash
})
)
.then(() => {
req.flash('success_messages', '成功註冊帳號')
res.redirect('/signin')
})
.catch(next)
},
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 = userController
7 changes: 7 additions & 0 deletions helpers/auth-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

module.exports = {
getUser: req => {
return req.user || null
},
ensureAuthenticated: req => { return req.isAuthenticated() }
}
16 changes: 16 additions & 0 deletions helpers/file-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const fs = require('fs') // 引入 fs 模組
const localFileHandler = file => {
// file 是 multer 處理完的檔案,以下將temp資料透過fs方法,複製一份到upload資料夾
return new Promise((resolve, reject) => {
if (!file) return resolve(null)
const fileName = `upload/${file.originalname}` // 圖片路徑
return fs.promises
.readFile(file.path)
.then(data => fs.promises.writeFile(fileName, data))
.then(() => resolve(`/${fileName}`))
.catch(err => reject(err))
})
}
module.exports = {
localFileHandler
}
4 changes: 4 additions & 0 deletions helpers/hbs-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const dayjs = require('dayjs') // 載入 dayjs 套件
module.exports = {
currentYear: dayjs().year() // 取得當年年份作為 currentYear 的屬性值,並導出
}
Loading