Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/getUser #880

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bf3b002
feat: add eslint
Nilney Aug 22, 2023
8e71d4b
feat: add account and cover to user model
Nilney Aug 23, 2023
efae12a
feat: init models
Nilney Aug 23, 2023
cd2aef7
feat: add passport
Nilney Aug 23, 2023
e0ea72d
Merge pull request #1 from Nilney/feature/initProject
Harrison0502 Aug 24, 2023
90975cf
feat: add users seed
Harrison0502 Aug 24, 2023
ef6b744
feat: add tweets seed
Harrison0502 Aug 24, 2023
31e5ea8
feat: add replies seed
Harrison0502 Aug 24, 2023
1dbe497
feat: add api error handler
Harrison0502 Aug 24, 2023
1045c5b
Merge pull request #2 from Nilney/feature/addSeedData
Nilney Aug 24, 2023
7bbec93
Merge pull request #3 from Nilney/feature/addErrorHandler
Nilney Aug 24, 2023
6bef641
feat: add admin signin api
Nilney Aug 24, 2023
c51e0c0
feat:add user signin api
Harrison0502 Aug 24, 2023
25f1981
Merge pull request #4 from Nilney/feature/adminSignin
Harrison0502 Aug 24, 2023
972fd7a
fix: update signin route
Harrison0502 Aug 24, 2023
39c8fb3
Merge branch 'master' of https://github.com/Nilney/twitter-api-2020 i…
Harrison0502 Aug 24, 2023
dd68385
feat: install bcrypt module
Harrison0502 Aug 24, 2023
3d6c203
refactor: login-handler and route/index
Harrison0502 Aug 24, 2023
cc95437
feat: add admin getUsers api
Nilney Aug 25, 2023
59d3c32
feat: add admin getTweets api
Nilney Aug 25, 2023
94ddf10
refactor: switched to bcryptjs for password encryption
Harrison0502 Aug 25, 2023
9633656
Merge pull request #5 from Nilney/feature/signin
Nilney Aug 25, 2023
82f0b3e
Merge branch 'master' into feature/getAllUsers
Nilney Aug 25, 2023
0d9f143
Merge branch 'master' into feature/getAllTweets
Nilney Aug 25, 2023
c8cac3f
Merge branch 'feature/getAllUsers' into feature/getAllTweets
Nilney Aug 25, 2023
e366de0
feat: modify config for heroku
Harrison0502 Aug 25, 2023
0d97949
Merge pull request #8 from Nilney/feature/herokuDeploy
Nilney Aug 25, 2023
496e9c9
feat: add dayjs
Nilney Aug 26, 2023
abaa495
Merge pull request #7 from Nilney/feature/getAllTweets
Harrison0502 Aug 26, 2023
e9fc631
feat: add cors package
Harrison0502 Aug 26, 2023
3668e78
Merge pull request #9 from Nilney/feature/addCors
Nilney Aug 26, 2023
be491b7
feat: add default name to user seed data
Harrison0502 Aug 26, 2023
b1d221e
Merge pull request #10 from Nilney/feature/addSeedData
Nilney Aug 26, 2023
412dbd3
feat: add admin delete tweet api
Nilney Aug 26, 2023
a143c81
refactor: restructure the signin return data
Harrison0502 Aug 26, 2023
a2a6355
refactor: restructure return data of admin signin
Nilney Aug 26, 2023
0af8260
Merge pull request #11 from Nilney/feature/signin
Nilney Aug 26, 2023
dba44a5
Merge pull request #12 from Nilney/feature/adminSignin
Harrison0502 Aug 26, 2023
b3bda18
Merge remote-tracking branch 'origin' into feature/deleteTweet
Nilney Aug 26, 2023
763f804
feat: add authenticatedUser module
Nilney Aug 26, 2023
3d411a6
feat: add get all tweets api
Nilney Aug 26, 2023
86a1146
fix: return data without user password
Nilney Aug 26, 2023
f64e90a
Merge pull request #14 from Nilney/feature/getTweets
Harrison0502 Aug 26, 2023
6f1d2ef
Merge pull request #13 from Nilney/feature/deleteTweet
Harrison0502 Aug 26, 2023
f706f02
feat: add date time format module
Nilney Aug 26, 2023
5f52609
feat: add get tweet api
Nilney Aug 26, 2023
0d2ba53
Merge branch 'master' into feature/getTweet
Nilney Aug 26, 2023
ee5b28e
feat: add signup api
Harrison0502 Aug 26, 2023
50ae427
Merge branch 'master' of https://github.com/Nilney/twitter-api-2020 i…
Harrison0502 Aug 26, 2023
6266232
Merge branch 'master' of https://github.com/Nilney/twitter-api-2020 i…
Harrison0502 Aug 26, 2023
6c0b3b6
feat: add getUser api
Harrison0502 Aug 27, 2023
11be5d4
feat: modify getUser api and include passport data association
Harrison0502 Aug 27, 2023
801da63
Merge pull request #15 from Nilney/feature/getTweet
Harrison0502 Aug 27, 2023
0c888b7
feat:add user default role:'user'
Harrison0502 Aug 27, 2023
6e7c20d
refactor: use async/await for asynchronous operations
Harrison0502 Aug 27, 2023
4ee2535
Merge pull request #16 from Nilney/feature/signup
Nilney Aug 27, 2023
8903510
Merge branch 'master' into feature/getUser
Nilney Aug 27, 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 @@
JWT_SECRET=
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules/*
/test/*
12 changes: 12 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
env:
browser: true
commonjs: true
es2021: true
extends:
- standard
parserOptions:
ecmaVersion: 12
rules:
arrow-parens:
- warn
- as-needed
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
8 changes: 4 additions & 4 deletions _helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

function getUser(req) {
return req.user;
function getUser (req) {
return req.user
}

module.exports = {
getUser,
};
getUser
}
23 changes: 18 additions & 5 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}

const express = require('express')
const helpers = require('./_helpers');
const helpers = require('./_helpers')
const passport = require('passport')
const router = require('./routes')
const cors = require('cors')

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

// use helpers.getUser(req) to replace req.user
function authenticated(req, res, next){
// passport.authenticate('jwt', { ses...
};
// function authenticated (req, res, next) {
// passport.authenticate('jwt', { ses...
// };

app.use(cors())
app.use(passport.initialize())
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
app.use('/api', router)

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Expand Down
1 change: 1 addition & 0 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"logging": false
},
"production": {
"use_env_variable": "MYSQL_DATABASE_URL",
"username": "root",
"password": null,
"database": "database_production",
Expand Down
25 changes: 24 additions & 1 deletion config/passport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
const passport = require('passport')
const passportJWT = require('passport-jwt')
const { User } = require('../models')

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

const jwtOptions = {
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
passReqToCallback: true
}

module.exports = passport
passport.use(new JWTStrategy(jwtOptions, async (req, jwtPayload, cb) => {
try {
const user = await User.findByPk(jwtPayload.id, {
include: [
{ model: User, as: 'Followings' }
]
})
req.user = user
cb(null, user)
} catch (err) {
cb(err)
}
}))

module.exports = passport
98 changes: 98 additions & 0 deletions controllers/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const jwt = require('jsonwebtoken')
const sequelize = require('sequelize')
const { getUser } = require('../_helpers')
const { User, Tweet, Reply, Like } = require('../models')
const { relativeTimeFromNow } = require('../helpers/dayjs-helpers')

const adminController = {
signIn: (req, res, next) => {
try {
const userData = getUser(req).toJSON()
delete userData.password
const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' })

res.json({
token,
user: userData
})
} catch (err) {
next(err)
}
},
getUsers: async (req, res, next) => {
try {
const users = await User.findAll({
attributes: [
'id',
'name',
'account',
'avatar',
'cover',
[
sequelize.literal('(SELECT COUNT(*) FROM Tweets WHERE Tweets.UserId = User.id)'), 'tweetsAmount'
],
[
sequelize.literal('(SELECT COUNT(*) FROM Likes WHERE Likes.TweetId IN (SELECT id FROM Tweets WHERE Tweets.UserId = User.id))'), 'likedAmount'
],
[
sequelize.literal('(SELECT COUNT(*) FROM Followships WHERE Followships.followerId = User.id)'), 'followingAmount'
],
[
sequelize.literal('(SELECT COUNT(*) FROM Followships WHERE Followships.followingId = User.id)'), 'followerAmount'
]
],
raw: true
})

res.status(200).json(users)
} catch (err) {
next(err)
}
},
getTweets: async (req, res, next) => {
try {
const tweets = await Tweet.findAll({
include: [
{
model: User,
attributes: ['id', 'account', 'name', 'avatar']
}
],
order: [['createdAt', 'DESC']],
raw: true,
nest: true
})
// 擷取推文訊息50字
const data = tweets.map(tweet => ({
...tweet,
description: tweet.description.substring(0, 50),
createdAt: relativeTimeFromNow(tweet.createdAt)
}))

res.status(200).json(data)
} catch (err) {
next(err)
}
},
deleteTweet: async (req, res, next) => {
try {
const tweet = await Tweet.findByPk(req.params.id)
if (!tweet) throw new Error('此推文不存在!')

// 刪除推文時連同該則推文底下的留言及喜歡都一起刪除
await tweet.destroy()
await Reply.destroy({ where: { TweetId: tweet.id } })
await Like.destroy({ where: { TweetId: tweet.id } })

res.status(200).json({
status: 'success',
message: '成功刪除推文!',
tweet
})
} catch (err) {
next(err)
}
}
}

module.exports = adminController
85 changes: 85 additions & 0 deletions controllers/tweet-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const { Tweet, User, Reply } = require('../models')
const { relativeTimeFromNow, formatDate, formatTime } = require('../helpers/dayjs-helpers')

const tweetController = {
getTweets: async (req, res, next) => {
try {
const tweets = await Tweet.findAll({
include: [
{
model: User,
attributes: ['id', 'account', 'name', 'avatar']
},
{ model: Reply },
{
model: User,
as: 'LikedUsers',
attributes: ['id', 'account', 'name']
}
],
order: [['createdAt', 'DESC']]
})

if (!tweets) throw new Error('目前沒有任何推文。')

const data = tweets.map(tweet => ({
...tweet.toJSON(),
createdAt: relativeTimeFromNow(tweet.createdAt), // 推文的時間以 相對時間 為概念
repliedAmount: tweet.Replies.length || 0,
likedAmount: tweet.LikedUsers.length || 0
}))

return res.status(200).json(data)
} catch (err) {
next(err)
}
},
getTweet: async (req, res, next) => {
try {
const tweet = await Tweet.findByPk(req.params.tweetId, {
include: [
{
model: User,
attributes: ['id', 'account', 'name', 'avatar']
},
{
model: Reply,
include: [
{ model: User, attributes: ['id', 'account', 'name', 'avatar'] }
]
},
{
model: User,
as: 'LikedUsers',
attributes: ['id', 'account', 'name', 'avatar']
}
],
order: [
[{ model: Reply }, 'createdAt', 'DESC'] // 推文的回覆依時間由新到舊排序
]
})

if (!tweet) throw new Error('推文不存在!')

// 回覆的時間以 相對時間 為概念
const repliesData = tweet.Replies.map(reply => ({
...reply.toJSON(),
createdAt: relativeTimeFromNow(reply.createdAt)
}))
const data = {
...tweet.toJSON(),
createdAtDate: formatDate(tweet.createdAt), // 提供推文需要的日期格式
createdAtTime: formatTime(tweet.createdAt), // 提供推文需要的時間格式
Replies: repliesData,
repliedAmount: tweet.Replies.length || 0,
likedAmount: tweet.LikedUsers.length || 0
}

return res.status(200).json(data)
} catch (err) {
next(err)
}
}
}

module.exports = tweetController
86 changes: 86 additions & 0 deletions controllers/user-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const helpers = require('../_helpers')
const { User } = require('../models')

const userController = {
signIn: (req, res, next) => {
try {
const userData = helpers.getUser(req).toJSON()
delete userData.password
const token = jwt.sign(userData, process.env.JWT_SECRET, { expiresIn: '30d' })// 簽發 JWT Token(期限30天)
return res.status(200).json({
token,
user: userData
})
} catch (err) {
return next(err)
}
},
signUp: async (req, res, next) => {
try {
const { account, name, email, password, checkPassword } = req.body
if (!name || !account || !email || !password || !checkPassword) throw new Error('所有欄位皆為必填!')
if (name.length > 50) throw new Error('名稱字數超出上限!')
if (password !== checkPassword) throw new Error('密碼與確認密碼不符合!')
const [userEmail, userName] = await Promise.all([
User.findOne({ where: { email } }),
User.findOne({ where: { account } })
])
if (userEmail) throw new Error('email已重複註冊!')
if (userName) throw new Error('account已重複註冊!')


const hash = await bcrypt.hash(password, 10)
const newUser = await User.create({
name,
account,
email,
role: 'user',
password: hash
})
const userData = newUser.toJSON()
delete userData.password
return res.json({
status: 'success',
data: { user: userData }
})
} catch (err) {
return next(err)
}
},
getUser: async (req, res, next) => {
try {
const UserId = req.params.id // 被查看的使用者ID
const isFollowed = helpers.getUser(req).Followings.some(f => f.id.toString() === UserId) || [] // 檢查用戶是否有被使用者追蹤
const currentUserId = helpers.getUser(req).id.toString() // 用戶ID
const isCurrentUser = currentUserId === UserId // 判斷是否是使用者本人
const user = await User.findByPk(UserId, {
attributes: { exclude: ['password'] },
include: [
{ model: User, as: 'Followers' },
{ model: User, as: 'Followings' }
]
})
if (!user || (user.role === 'admin')) {
const err = new Error('使用者不存在!')
err.status = 404
throw err
}
const result = {
...user.toJSON(),
followersCount: user.Followers.length,
followingsCount: user.Followings.length,
isCurrentUser,
isFollowed
}
delete result.Followers
delete result.Followings
return res.json(result)
} catch (err) {
next(err)
}
}
}

module.exports = userController
11 changes: 11 additions & 0 deletions helpers/dayjs-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const dayjs = require('dayjs')
const relativeTime = require('dayjs/plugin/relativeTime')
require('dayjs/locale/zh-tw')
dayjs.locale('zh-tw')
dayjs.extend(relativeTime)

module.exports = {
relativeTimeFromNow: a => dayjs(a).fromNow(),
formatDate: a => dayjs(a).format('YYYY年MM月DD日'),
formatTime: a => dayjs(a).format('A h:mm').replace('AM', '上午').replace('PM', '下午')
}
Loading