Skip to content
Open

R05 #64

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 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
96da4be
feat:add R02.test.js
Carrot7712 Jan 13, 2022
37322b4
feat:add R03.test.js
Carrot7712 Jan 13, 2022
ad796af
feat:add R04.test.js
Carrot7712 Jan 13, 2022
7ece79e
add R01.test.js && unit-test-helper.js
AmberYen Mar 2, 2022
b54a2e9
add R02.test.js file
AmberYen Mar 2, 2022
ef44c3e
add R03.test.js file
AmberYen Mar 2, 2022
1ead393
add R04.test.js
AmberYen Mar 2, 2022
4bce0c5
add R05.test.js
AmberYen Mar 2, 2022
79ff158
註解 typo 修正
zjzheng17 Jul 18, 2022
9d566b6
correcting typo in comment
zjzheng17 Jul 18, 2022
4fe60b0
correcting typo in comment
zjzheng17 Jul 18, 2022
03c14c0
correcting typo in comment
zjzheng17 Jul 18, 2022
326bed8
fix R01.test.js comment typo
tuterwell Aug 10, 2023
7f6510a
fix R01.test.js comment typo
tuterwell Aug 10, 2023
d5b1553
chore: init github workflow
eugenechen0514 Sep 9, 2023
6f29ed3
chore: init github workflow
eugenechen0514 Sep 9, 2023
02a746f
chore: init github workflow
eugenechen0514 Sep 9, 2023
16bd368
chore: init github workflow
eugenechen0514 Sep 9, 2023
afe4d45
chore: init github workflow
eugenechen0514 Sep 9, 2023
fe01b7f
Update unit-test-helper.js
tuterwell Sep 12, 2023
13c29eb
Update unit-test-helper.js update()
tuterwell Sep 12, 2023
e2665da
chore: format code
eugenechen0514 Sep 17, 2023
1be932b
test: fix update method
eugenechen0514 Sep 17, 2023
d12a942
Merge commit '1be932b9d1998168679d2cdf5a7a9e2f1d2bca91' into R01-test
eugenechen0514 Sep 17, 2023
20ab723
Merge branch 'R01-test' into R02-test
eugenechen0514 Sep 17, 2023
12487dc
Merge branch 'R02-test' into R03-test
eugenechen0514 Sep 17, 2023
2942b99
Merge branch 'R03-test' into R04-test
eugenechen0514 Sep 17, 2023
8e195f8
Merge branch 'R04-test' into R05-test
eugenechen0514 Sep 17, 2023
f3b0c32
Delete .travis.yml
tuterwell Sep 18, 2023
a449027
Delete .travis.yml
tuterwell Sep 18, 2023
c26e8ee
Delete .travis.yml
tuterwell Sep 18, 2023
07201da
Delete .travis.yml
tuterwell Sep 18, 2023
95105ff
add hbs
f88643 Dec 8, 2023
543a362
add index page
f88643 Dec 10, 2023
5277b84
add admin index page
f88643 Dec 10, 2023
c33a928
flash msg $signup verification
f88643 Dec 11, 2023
571faec
header & footer
f88643 Dec 11, 2023
1d39924
faker
f88643 Dec 12, 2023
3685bae
test
f88643 Dec 13, 2023
8da2b57
test update
f88643 Dec 13, 2023
d4b8742
database?
f88643 Dec 13, 2023
fd9f986
restaurant
f88643 Dec 13, 2023
b532543
restaurant
f88643 Dec 13, 2023
d7108fd
Restaurant
f88643 Dec 13, 2023
9b9737e
Restaurant
f88643 Dec 13, 2023
af8e818
Restaurant
f88643 Dec 14, 2023
d16b3c7
Restaurant
f88643 Dec 14, 2023
3b65df6
Restaurant
f88643 Dec 14, 2023
81e7bf4
seed update
f88643 Dec 14, 2023
3e20df2
render category
f88643 Dec 14, 2023
d380d79
test update
f88643 Dec 15, 2023
1c5478b
test update
f88643 Dec 15, 2023
3d14ec2
test update
f88643 Dec 15, 2023
d77deb8
test update
f88643 Dec 15, 2023
f78c470
test update
f88643 Dec 15, 2023
0e11625
test update
f88643 Dec 15, 2023
aa3ab72
test update
f88643 Dec 15, 2023
b0fdd1b
test update
f88643 Dec 15, 2023
f78b619
add categories
f88643 Dec 15, 2023
873f9bb
test
f88643 Dec 16, 2023
7301de8
test
f88643 Dec 16, 2023
e96aae6
test
f88643 Dec 18, 2023
14e22de
Merge branch 'R01' of https://github.com/f88643/forum-express-grading…
f88643 Dec 18, 2023
4d71835
test
f88643 Dec 18, 2023
573562d
test
f88643 Dec 18, 2023
5e8b4bc
test
f88643 Dec 18, 2023
aa0e9d1
test
f88643 Dec 18, 2023
20e6c3e
test
f88643 Dec 18, 2023
d59dc3d
test
f88643 Dec 18, 2023
248091f
add dasgboard
f88643 Dec 18, 2023
954f919
add pagination
f88643 Dec 19, 2023
512ae7f
add comment
f88643 Dec 19, 2023
0305b21
add comment
f88643 Dec 19, 2023
c6f51d5
R02
f88643 Dec 19, 2023
030eba0
view comment
f88643 Dec 19, 2023
45749aa
add user profile
f88643 Dec 20, 2023
5b3155c
Merge branch 'R03-test' of https://github.com/ALPHACamp/forum-express…
f88643 Dec 20, 2023
11e8ae3
R03
f88643 Dec 20, 2023
c740327
comments count
f88643 Dec 20, 2023
17ea957
add feeds page
f88643 Dec 21, 2023
aeee5af
add favorite/delete button
f88643 Dec 21, 2023
02da069
switch favorite/delete button
f88643 Dec 21, 2023
8604af1
switch favorite/delete button
f88643 Dec 21, 2023
409e957
R04
f88643 Dec 22, 2023
f341f77
Merge remote-tracking branch 'upstream/R04-test' into R04
f88643 Dec 22, 2023
61b9d31
following function
f88643 Dec 22, 2023
d9e2dde
Merge remote-tracking branch 'upstream/R05-test' into R05
f88643 Dec 22, 2023
683a464
R05
f88643 Dec 23, 2023
2bc17d6
R05
f88643 Dec 23, 2023
24a956a
???
f88643 Dec 23, 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
37 changes: 14 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
name: Node.js test

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

env:
NODE_ENV: github

jobs:
test:

runs-on: ubuntu-latest

strategy:
Expand All @@ -28,26 +23,22 @@ jobs:
steps:
- uses: mirromutth/mysql-action@v1.1
with:
host port: 3306 # Optional, default value is 3306. The port of host
container port: 3307 # Optional, default value is 3306. The port of container
character set server: 'utf8mb4' # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld
collation server: 'utf8mb4_unicode_ci' # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld
mysql version: '8.0' # Optional, default value is "latest". The version of the MySQL
mysql database: 'forum' # Optional, default value is "test". The specified database which will be create
mysql root password: 'password' # Required if "mysql user" is empty, default is empty. The root superuser password
host port: 3306 # Optional, default value is 3306. The port of host
container port: 3307 # Optional, default value is 3306. The port of container
character set server: "utf8mb4" # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld
collation server: "utf8mb4_unicode_ci" # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld
mysql version: "8.0" # Optional, default value is "latest". The version of the MySQL
mysql database: "forum" # Optional, default value is "test". The specified database which will be create
mysql root password: "password" # Required if "mysql user" is empty, default is empty. The root superuser password
# 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:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache: "npm"
- run: npm install
- run: npm install sequelize-cli -g
- run: sequelize db:migrate
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Node.js test

on:
push:
branches:
# - main
- "*-test"
pull_request:
branches:
# - main

env:
NODE_ENV: github

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [14.x] # ex: [16.x, 18.x], See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: mirromutth/mysql-action@v1.1
with:
host port: 3306 # Optional, default value is 3306. The port of host
container port: 3307 # Optional, default value is 3306. The port of container
character set server: "utf8mb4" # Optional, default value is 'utf8mb4'. The '--character-set-server' option for mysqld
collation server: "utf8mb4_unicode_ci" # Optional, default value is 'utf8mb4_general_ci'. The '--collation-server' option for mysqld
mysql version: "8.0" # Optional, default value is "latest". The version of the MySQL
mysql database: "forum" # Optional, default value is "test". The specified database which will be create
mysql root password: "password" # Required if "mysql user" is empty, default is empty. The root superuser password
# 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
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install
- run: npm install sequelize-cli -g
- run: sequelize db:migrate
- run: sequelize db:seed:all
- run: npm test
30 changes: 29 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
const path = require('path')
const express = require('express')
const handlebars = require('express-handlebars')
const flash = require('connect-flash')
const methodOverride = require('method-override')
const session = require('express-session')
const passport = require('./config/passport')
const handlebarsHelpers = require('./helpers/handlebars-helpers')
const { getUser } = require('./helpers/auth-helpers')
const routes = require('./routes')


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(methodOverride('_method'))
app.use(express.urlencoded({ extended: true }))

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('/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, () => {
console.info(`Example app listening on port ${port}!`)
console.info(`Example app listening on port http://localhost:${port}`)
})

module.exports = app
44 changes: 44 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const passport = require('passport')
const LocalStrategy = require('passport-local')
const bcrypt = require('bcryptjs')
const { User, Restaurant } = require('../models')


passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
(req, username, password, cb) => {
User.findOne({ where: { email: username } })
.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)
})
})
}
))

passport.serializeUser((user, cb) => {
cb(null, user.id)
})

passport.deserializeUser((id, cb) => {
return User.findByPk(id, {
include: [
{ model: Restaurant, as: 'FavoritedRestaurants' },
{ model: Restaurant, as: 'LikedRestaurants' },
{ model: User, as: 'Followers' },
{ model: User, as: 'Followings' }
]
})
.then(user => cb(null, user.toJSON()))
.catch(err => cb(err))
})


module.exports = passport
136 changes: 136 additions & 0 deletions controllers/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const { Restaurant, User, Category } = require('../models')
const { localFileHandler } = require('../helpers/file-helpers')

const adminController = {
getRestaurants: (req, res, next) => {
Restaurant.findAll({
raw: true,
nest: true, // restaurant['category.name'] => restaurant.category.name
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

localFileHandler(file)
.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) => {
Restaurant.findByPk(req.params.id, { // 去資料庫用 id 找一筆資料
raw: true, // 找到以後整理格式再回傳
nest: true,
include: [Category]

})
.then(restaurant => {
if (!restaurant) throw new Error("Restaurant didn't exist!") // 如果找不到,回傳錯誤訊息,後面不執行
res.render('admin/restaurant', { restaurant })
})
.catch(err => next(err))
},
editRestaurant: (req, res, next) => {
Promise.all([
Restaurant.findByPk(req.params.id, { raw: true }),
Category.findAll({ raw: true })
])
.then(([restaurant, categories]) => {
if (!restaurant) throw new Error("Restaurant didn'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), // 去資料庫查有沒有這間餐廳
localFileHandler(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 updated')
res.redirect('/admin/restaurants')
})
},
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(() => {
req.flash('success_messages', 'restaurant was successfully deleted')
res.redirect('/admin/restaurants')
})
.catch(err => next(err))
},
getUsers: (req, res, next) => {
return User.findAll({
raw: true
})
.then(users => {
res.render('admin/users', { users }
)
})
.catch(err => next(err))
},

patchUser: (req, res, next) => {
return User.findByPk(req.params.id) // 去資料庫查有沒有這間user
.then(user => {
if (!user) throw new Error("User didn'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
48 changes: 48 additions & 0 deletions controllers/category-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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 // 從 req.body 拿出表單裡的資料
if (!name) throw new Error('Category name is required!') // name 是必填,若發先是空值就會終止程式碼,並在畫面顯示錯誤提示
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: (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
Loading