Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 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
fa173a7
chore: fix code format
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
1b8cd24
Delete .travis.yml
tuterwell Sep 18, 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
5931546
Feat: add handlebars
brownishgreen Sep 7, 2024
2bd3f7f
feat: add index page
brownishgreen Sep 7, 2024
50642a2
feat: add admin index page
brownishgreen Sep 7, 2024
525433d
feat: add user model
brownishgreen Sep 8, 2024
a4cc5b7
feat: user signup
brownishgreen Sep 9, 2024
9a5f36d
feat: flash msg & signup verification
brownishgreen Sep 9, 2024
49567c8
passport init & sign in
brownishgreen Sep 10, 2024
5acc2cb
feat: user model add is_admin
brownishgreen Sep 10, 2024
76a92b1
add restaurant model
brownishgreen Sep 11, 2024
bfd079f
feat: modify admin restaurants page
brownishgreen Sep 11, 2024
612f59a
feat: create restaurant
brownishgreen Sep 11, 2024
684af4c
feat: admin restaurant page
brownishgreen Sep 11, 2024
5cbddbb
feat: admin update restaurant
brownishgreen Sep 12, 2024
7b4444d
feat: delete restaurant
brownishgreen Sep 12, 2024
fa8c7f4
feat: add restaurant image
brownishgreen Sep 14, 2024
80f6b54
feat: add seed data
brownishgreen Sep 14, 2024
bb432f2
feat: adjust middleware/auth.js
brownishgreen Sep 16, 2024
542f6dc
feat:integrate Imgur API for restaurant image upload
brownishgreen Sep 16, 2024
b63530c
feat: implement login functionality and user role management
brownishgreen Sep 16, 2024
ba541ed
feat(admin): add nav links to Restaurants and Users in the panel
brownishgreen Sep 16, 2024
aecbeb8
adjust flash message to qualify test
brownishgreen Sep 17, 2024
8de99c4
feat: add category model
brownishgreen Sep 17, 2024
c22f38d
feat: update seed files
brownishgreen Sep 17, 2024
493eb52
feat: show category on admin restaurant pages
brownishgreen Sep 17, 2024
a4a3b74
feat: add categories selector on admin create and edit page
brownishgreen Sep 17, 2024
f27c14c
feat: add ifCond hbs helper & update category selector
brownishgreen Sep 17, 2024
292c659
add admin categories page
brownishgreen Sep 17, 2024
942a34c
feat: category create
brownishgreen Sep 17, 2024
8cdd548
feat: category update
brownishgreen Sep 17, 2024
7a1b3b3
feat: category delete
brownishgreen Sep 17, 2024
966f988
feat: add restaurants index page
brownishgreen Sep 17, 2024
aec45c7
feat: add restaurant page
brownishgreen Sep 17, 2024
c1e4b72
feat: add restaurant dashboard
brownishgreen Sep 18, 2024
2770735
feat: add categories navbar on restaurants index page
brownishgreen Sep 18, 2024
9ce3cc6
feat: add pagination on restaurants index page
brownishgreen Sep 18, 2024
9da7326
feat: add comment model
brownishgreen Sep 19, 2024
2408d78
addd post comment feature on restaurant page
brownishgreen Sep 19, 2024
cc8aa15
feat: show comments on restaurant page
brownishgreen Sep 19, 2024
d9b6497
feat: add delete comment for admin
brownishgreen Sep 19, 2024
dd09042
Merge remote-tracking branch 'upstream/R02-test' into R02
brownishgreen Sep 20, 2024
8fc4380
update dashboard
brownishgreen Sep 20, 2024
2789c32
Merge remote-tracking branch 'upstream/R03-test' into R03
brownishgreen Sep 20, 2024
3c74844
feat: add user profile /edit
brownishgreen Sep 21, 2024
6a3d8f1
feat: add feeds page
brownishgreen Sep 21, 2024
4c82570
feat: create favorite model
brownishgreen Sep 21, 2024
72ddd86
feat: add favorite/unfavorite button at index page
brownishgreen Sep 21, 2024
cf23901
feat: get user's favorited restaurant & switch button
brownishgreen Sep 22, 2024
6ce8f64
Merge remote-tracking branch 'upstream/R04-test' into R04
brownishgreen Sep 22, 2024
e0d48cc
feat: like/unlike
brownishgreen Sep 22, 2024
5e21eb0
feat: create followship model
brownishgreen Sep 22, 2024
87bdd9b
feat: add topUser page
brownishgreen Sep 22, 2024
9673110
feat: addFollowing & removeFollowing function
brownishgreen Sep 22, 2024
bec4be7
feat: top 10 restaurants
brownishgreen Sep 22, 2024
4787a78
update controller to fir R05 test :S
brownishgreen Sep 22, 2024
be2920f
feat: initialize api server
brownishgreen Sep 25, 2024
82d325e
feat: add getRestaurants api
brownishgreen Sep 25, 2024
179997f
feat: add restaurant-services
brownishgreen Sep 27, 2024
34a5411
update restaurant-services
brownishgreen Sep 28, 2024
edff72a
feat: add create restaurant api
brownishgreen Sep 28, 2024
a8322ce
feat: api signin add jwt token
brownishgreen Sep 29, 2024
ee6999e
feat: add api-auth middleware
brownishgreen Sep 29, 2024
9997d47
feat:modify api unauthorize response
brownishgreen Sep 29, 2024
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 @@
JWT_SECRET=
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Node.js test

on:
<<<<<<< HEAD
pull_request_target:
branches:
- main
Expand All @@ -11,6 +12,14 @@ on:
# - '*-test'
# pull_request:
# branches:
=======
push:
branches:
# - main
- '*-test'
pull_request:
branches:
>>>>>>> upstream/R01-test
# - main

env:
Expand Down Expand Up @@ -38,11 +47,15 @@ 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
<<<<<<< HEAD
- 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
>>>>>>> upstream/R01-test
- 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,10 +1,40 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}
const path = require('path')
const express = require('express')
const routes = require('./routes')
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') // 引入passport
const { getUser } = require('./helpers/auth-helpers')
const { pages, apis } = require('./routes')
const handlebarsHelpers = require('./helpers/handlebars-helpers')

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

app.use(routes)
app.engine('hbs', handlebars({ extname: '.hbs', helpers: handlebarsHelpers }))
app.set('view engine', 'hbs')
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
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('/api', apis)
app.use(pages)

app.listen(port, () => {
console.info(`Example app listening on port ${port}!`)
Expand Down
5 changes: 3 additions & 2 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"host": "127.0.0.1",
"dialect": "mysql"
},
"travis": {
"username": "travis",
"github": {
"username": "root",
"password": "password",
"database": "forum",
"host": "127.0.0.1",
"dialect": "mysql",
Expand Down
63 changes: 63 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const passport = require('passport')
const LocalStrategy = require('passport-local')
const passportJWT = require('passport-jwt')
const bcrypt = require('bcryptjs')
const { User, Restaurant } = require('../models')

const JWTStrategy = passportJWT.Strategy
const ExtractJWT = passportJWT.ExtractJwt

const jwtOptions = {
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
}
passport.use(new JWTStrategy(jwtOptions, (jwtPayload, cb) => {
User.findByPk(jwtPayload.id, {
include: [
{ model: Restaurant, as: 'FavoritedRestaurants' },
{ model: Restaurant, as: 'LikedRestaurants' },
{ model: User, as: 'Followers' },
{ model: User, as: 'Followings' }
]
})
.then(user => cb(null, user))
.catch(err => cb(err))
}))

// 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, {
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
15 changes: 15 additions & 0 deletions controllers/apis/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const adminServices = require('../../services/admin-services')

const adminController = {
getRestaurants: (req, res, next) => {
adminServices.getRestaurants(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
deleteRestaurant: (req, res, next) => {
adminServices.deleteRestaurant(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
postRestaurant: (req, res, next) => {
adminServices.postRestaurant(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
}
}

module.exports = adminController
8 changes: 8 additions & 0 deletions controllers/apis/restaurant-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const restaurantServices = require('../../services/restaurant-services')

const restaurantController = {
getRestaurants: (req, res, next) => {
restaurantServices.getRestaurants(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
}
}
module.exports = restaurantController
21 changes: 21 additions & 0 deletions controllers/apis/user-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require('dotenv').config()
const jwt = require('jsonwebtoken')
const userController = {
signIn: (req, res, next) => {
try {
const userData = req.user.toJSON()
delete userData.password
const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' }) // 簽發 JWT,效期為 30 天
res.json({
status: 'success',
data: {
token,
user: userData
}
})
} catch (err) {
next(err)
}
}
}
module.exports = userController
111 changes: 111 additions & 0 deletions controllers/pages/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const adminServices = require('../../services/admin-services')
const { Restaurant, User, Category } = require('../../models')
const { imgurFileHandler } = require('../../helpers/file-helpers')

const adminController = {
getUsers: (req, res, next) => {
return User.findAll({
raw: true,
nest: true,
attributes: ['id', 'name', 'email', 'password', 'isAdmin', 'createdAt', 'updatedAt']
})
.then(users => {
console.log(users)
res.render('admin/users', { users })
})
.catch(err => next(err))
},
patchUser: (req, res, next) => {
console.log(`接收到 PATCH 請求,使用者 ID:${req.params.id}`)

return User.findByPk(req.params.id)
.then(user => {
if (!user) throw new Error('User not found!')
if (user.email === 'root@example.com') {
req.flash('error_messages', '禁止變更 root 權限')
return res.redirect('back')
}
return user.update({ isAdmin: !user.isAdmin })
})
.then(updatedUser => {
console.log(`新的使用者 isAdmin 狀態:${updatedUser.isAdmin}`)
req.flash('success_messages', '使用者權限變更成功')
return res.redirect('/admin/users')
})
.catch(err => {
console.error('更新使用者權限時出錯:', err)
next(err)
})
},
getRestaurants: (req, res, next) => {
adminServices.getRestaurants(req, (err, data) => err ? next(err) : res.render('admin/restaurants', data))
},
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) => {
adminServices.postRestaurant(req, (err, data) => {
if (err) return next(err)
req.flash('success_messages', 'Restaurant was successfully created!')
res.redirect('/admin/restaurants', { status: 'success', data })
})
},
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) => {
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
Promise.all([
Restaurant.findByPk(req.params.id),
imgurFileHandler(file)
])
.then(([restaurant, filePath]) => {
if (!restaurant) throw new Error('Restaurant did not exist!')
return restaurant.update({
name,
tel,
address,
openingHours,
description,
image: filePath || restaurant.image,
categoryId
})
})
.then(() => {
req.flash('success_messages', 'Restaurant was updated successfully!')
res.redirect('/admin/restaurants')
})
.catch(err => next(err))
},
deleteRestaurant: (req, res, next) => {
adminServices.deleteRestaurant(req, (err, data) => err ? next(err) : res.redirect('/admin/restaurants', data))
}

}
module.exports = adminController
46 changes: 46 additions & 0 deletions controllers/pages/category-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 does not exist!')
return category.update({ name })
})
.then(() => {
res.redirect('/admin/categories')
})
.catch(err => next(err))
},
deleteCategory: (req, res, next) => {
Category.findByPk(req.params.id)
.then(category => {
if (!category) throw new Error('Category does not exist!')
return category.destroy()
})
.then(() => res.redirect('/admin/categories'))
.catch(err => next(err))
}
}
module.exports = categoryController
Loading