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

前後端分離組 : 宗豪 , Ivy Wang , 簡宏倫 ,Blade #923

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
58e88ae
feat: models test
blade8128ch Oct 31, 2023
d89d949
feat: models test - OK
blade8128ch Nov 1, 2023
30a619c
feat: seeder_test & add admin-services_test
blade8128ch Nov 1, 2023
bac6966
Merge pull request #1 from Antarctic-penguin/feature/models
blade8128ch Nov 1, 2023
13cf4d2
heroku test
blade8128ch Nov 1, 2023
50e98b8
heroku test01
blade8128ch Nov 1, 2023
37a44bc
heroku test02
blade8128ch Nov 1, 2023
6216868
heroku test03
blade8128ch Nov 1, 2023
9f6cbbd
heroku test04
blade8128ch Nov 1, 2023
402b57e
heroku test05
blade8128ch Nov 1, 2023
f6dbaf7
heroku test06
blade8128ch Nov 1, 2023
fe2055f
heroku test07
blade8128ch Nov 1, 2023
9d0b93b
heroku test09
blade8128ch Nov 1, 2023
4c198ed
heroku test09 update mysql2
blade8128ch Nov 2, 2023
ab0f8c9
update user seeder & API /api/admin/tweets
blade8128ch Nov 3, 2023
e3392f8
update user_seeder passport & API /api/admin/tweets
blade8128ch Nov 3, 2023
917af43
feature : login api
blade8128ch Nov 4, 2023
b75eb18
admin
Antarctic-penguin Nov 5, 2023
1269745
feature : signup api
blade8128ch Nov 5, 2023
877efb4
updated equal to feature/admin-api
blade8128ch Nov 5, 2023
5df63ea
tmp
blade8128ch Nov 6, 2023
f10c2ee
fix cors
blade8128ch Nov 6, 2023
2bedfb4
fix authenticated
blade8128ch Nov 6, 2023
f405de8
feature: get api_users_id
blade8128ch Nov 6, 2023
42502a9
Postman中admin的那幾支跟tweet的前三支
Antarctic-penguin Nov 8, 2023
aeec7d7
Merge pull request #3 from Antarctic-penguin/R04
blade8128ch Nov 8, 2023
cf41413
merge to master & put_users ongoin
blade8128ch Nov 8, 2023
4c5b9f1
merge test
blade8128ch Nov 8, 2023
fbdee73
feature: user edit user API
blade8128ch Nov 8, 2023
07505dc
admin_ok tweet_ok users_ongoing
blade8128ch Nov 8, 2023
971f675
1109 temp
blade8128ch Nov 9, 2023
2fcfb5b
將推文時間轉換成幾小時前格式
Antarctic-penguin Nov 10, 2023
1dc0991
Merge pull request #4 from Antarctic-penguin/R04
blade8128ch Nov 10, 2023
5f27312
api_user ok and fix time
blade8128ch Nov 10, 2023
fe6c2be
fix user signup repeat
blade8128ch Nov 10, 2023
409c8e0
api followship OK
blade8128ch Nov 10, 2023
99ab34e
reply Ok & followship OK
blade8128ch Nov 10, 2023
8bb0930
reply OK & followship OK & fix seeder
blade8128ch Nov 10, 2023
46a808f
reply OK & followship OK & fix seeder
blade8128ch Nov 10, 2023
5005dc7
fix reply seeder
blade8128ch Nov 10, 2023
671a7a5
fix getFollowersTop bug root
blade8128ch Nov 10, 2023
2039d73
fix tweet unlike&like OK
blade8128ch Nov 11, 2023
5a2a0ea
Merge branch 'master' into feature/users
blade8128ch Nov 11, 2023
4459f63
fix jwt secret key
blade8128ch Nov 11, 2023
8d52742
Update passport.js _ fix secret key
blade8128ch Nov 11, 2023
8972dc2
Create README.md
blade8128ch Nov 11, 2023
3843ba8
test
blade8128ch Nov 11, 2023
91b4e06
clean code
blade8128ch Nov 11, 2023
9aa87f3
fix tweets time order & language
blade8128ch Nov 11, 2023
5af4066
fix admin users response
blade8128ch Nov 12, 2023
618dfab
fix admin users order
blade8128ch Nov 12, 2023
d13da8d
quick fix test
blade8128ch Nov 12, 2023
6031ecf
quick fix test
blade8128ch Nov 12, 2023
d4d3581
Merge pull request #6 from Antarctic-penguin/feature/users
blade8128ch Nov 12, 2023
9176de0
Merge pull request #7 from Antarctic-penguin/blade8128ch-patch-1
blade8128ch Nov 12, 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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
IMGUR_CLIENT_ID=
JWT_SECRET=
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.DS_Store
*.DS_Store

# multer
temp/
upload/

# Logs
logs
*.log
Expand Down
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
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# twitter-api-2020
ALPHA Camp | 學期 3 | Simple Twitter | 自動化測試檔 (前後分離組)

採用前後分離的開發模式
此專案為後端API

功能
---------------
帳號註冊與登入
使用者可以瀏覽,回覆,按讚貼文
使用者可以追蹤其他使用者,亦可取追蹤
使用者可以瀏覽其他使用者的個人頁面
使用者可以編輯個人資料或帳號資料
後台管理者可以瀏覽推文清單,也可刪除推文
後台管理者可以瀏覽使用者名單

安裝與執行專案
--------------
1. 確認已安裝node.js與npm
2. 下載此專案
```
git clone https://github.com/Antarctic-penguin/twitter-api-2020.git
```
3. 在專案路徑下安裝套件
```
npm install
```
4. 參考 .env.example 檔案,建立 .env 檔案並設定相關環境變數
5. 檢查/config/config.json ,設定對應的資料庫連線資訊
6. 建立資料表
```
npx sequelize db:migrate
```
7. 建立種子資料
```
npx sequelize db:seed:all
```
8.啟動專案
```
nodemon app.js
```




6 changes: 5 additions & 1 deletion _helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
function ensureAuthenticated(req){
return req.isAuthenticated()
}

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

module.exports = {
getUser,
ensureAuthenticated,
getUser
};
35 changes: 29 additions & 6 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}
const path = require('path')
const express = require('express')
const helpers = require('./_helpers');
const cors = require('cors')
const passport = require('./config/passport')
const methodOverride = require('method-override')
//const helpers = require('./_helpers')
const { getUser } = require('./_helpers')
const { apis } = require('./routes')

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

app.use(express.urlencoded({ extended: true }))
app.use(express.json())

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

app.get('/', (req, res) => res.send('Hello World!'))
app.use(cors())
app.use('/upload', express.static(path.join(__dirname, 'upload')))
app.use(methodOverride('_method'))
app.use((req, res, next) => {

res.locals.user = getUser(req)
next()
})

//app.get('/', (req, res) => res.send('Hello World!'))

app.use('/api', apis)
app.listen(port, () => console.log(`Example app listening on port ${port}!`))



module.exports = app
6 changes: 1 addition & 5 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
"logging": false
},
"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 Down
41 changes: 40 additions & 1 deletion config/passport.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
const passport = require('passport')
const LocalStrategy = require('passport-local')
const passportJWT = require('passport-jwt')
const bcrypt = require('bcryptjs')
const { User } = require('../models')

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


module.exports = passport
passport.use(new LocalStrategy(
// customize user field
{
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
},
// authenticate user or admin
(req, account, password, cb) => {
User.findOne({ where: { account } })
.then(user => {
if (!user) return cb(Error('帳號不存在'), false)

bcrypt.compare(password, user.password).then(res => {
if (!res) return cb(Error('密碼輸入錯誤!'), false)

return cb(null, user)
})
})
}
))

const jwtOptions = {
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET || 'alphacamp'
}

passport.use(new JWTStrategy(jwtOptions, (jwtPayload, cb) => {
User.findByPk(jwtPayload.id)
.then(user => cb(null, user))
.catch(err => cb(err))
}))

module.exports = passport
16 changes: 16 additions & 0 deletions controllers/apis/admin-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const adminServices = require('../../services/admin-services')
const adminController = {
getTweets: (req, res, next) => {
adminServices.getTweets(req, (err, data) => err ? next(err) : res.json(data))
},
postTweet: (req, res, next) => {
adminServices.postTweet(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
deleteTweet: (req, res, next) => {
adminServices.deleteTweet(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
getUsers: (req, res, next) => {
adminServices.getUsers(req, (err, data) => err ? next(err) : res.json(data))
},
}
module.exports = adminController
93 changes: 93 additions & 0 deletions controllers/apis/followship-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const dayjs = require('dayjs')
const relativeTime = require('dayjs/plugin/relativeTime');
dayjs.extend(relativeTime);
const helpers = require('../../_helpers')
const { User, Tweet, Reply, Like, Followship, sequelize } = require('../../models')
const { Op } = require("sequelize");

const followshipController = {
addFollowing: (req, res, next) => {
const followingId = req.body.id
const followerId = helpers.getUser(req).id
Promise.all([
User.findByPk(followerId),
Followship.findOne({
where: { followingId, followerId }
})
])
.then(([user, followship]) => {
if (!user) {
throw new Error('user not found')
}
if (followship) {
throw new Error('had followed')
}
if (user.id === Number(followingId)) {
throw new Error('no follow yourself')
}
return Followship.create({
followingId,
followerId
})
})
.then((response) => {
response = response.toJSON()
res.json({
status: 'success',
data: response
})
})
.catch(err => next(err))
},
removeFollowing: (req, res, next) => {
const followingId = req.params.followingId
const followerId = helpers.getUser(req).id
return Followship.findOne({
where: {
followingId: followingId,
followerId: followerId
}
})
.then(followship => {
if (!followship) throw new Error('you are not following')
return followship.destroy()
})
.then((response) => {
response = response.toJSON()
res.json({
status: 'success',
data: response
})
})
.catch(err => next(err))
},
getFollowersTop: (req, res, next) => {
Promise.all([
User.findAll({
where: {
role: { [Op.ne]: 'admin' }
}
}),
Followship.findAll()
]).then(([users, followships]) => {

for (let i = 0; i < users.length; i++) {
users[i].dataValues.count = 0
for (let j = 0; j < followships.length; j++) {
if (users[i].dataValues.id == followships[j].followingId) {
users[i].dataValues.count += 1
}
}
}
users.sort((a, b) => b.dataValues.count - a.dataValues.count)

res.json(
users
)
})
},

}
module.exports = followshipController
83 changes: 83 additions & 0 deletions controllers/apis/tweet-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const tweetServices = require('../../services/tweet-services')
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const dayjs = require('dayjs')
const relativeTime = require('dayjs/plugin/relativeTime');
dayjs.extend(relativeTime);
const helpers = require('../../_helpers')
const { User, Tweet, Reply, Like, Followship, sequelize } = require('../../models')
const { Op } = require("sequelize");

const tweetController = {
getTweets: (req, res, next) => {
tweetServices.getTweets(req, (err, data) => err ? next(err) : res.json(data))
},
getTweet: (req, res, next) => {
tweetServices.getTweet(req, (err, data) => err ? next(err) : res.json(data))
},
postTweet: (req, res, next) => {
tweetServices.postTweet(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
getTweetReplies: (req, res, next) => {

const tweetId = req.params.tweetId

Promise.all([
User.findAll({
}),
Reply.findAll({
where: { TweetId: tweetId },
order: [['createdAt', 'DESC']],
})
]).then(([users, comments]) => {

for (let i = 0; i < comments.length; i++) {

for (let j = 0; j < users.length; j++) {
if (comments[i].dataValues.UserId == users[j].dataValues.id) {
comments[i].dataValues.User = users[j].dataValues
}
}
}
for (let i = 0; i < comments.length; i++) {
const createdAtDate = dayjs(comments[i].createdAt);
const updatedAtDate = dayjs(comments[i].updatedAt);
comments[i].dataValues.createdAt = createdAtDate.fromNow()
comments[i].dataValues.updatedAt = updatedAtDate.fromNow()
}
res.json(
comments
)
})
},
postTweetReply: (req, res, next) => {
const TweetId = req.params.tweetId
const UserId = helpers.getUser(req).id
const comment = req.body.comment
if (!comment) throw new Error('comment is empty')

return Tweet.findByPk(TweetId)
.then(tweet => {
if (!tweet) throw new Error('tweet not found')
return Reply.create({
comment,
UserId,
TweetId
})
.then(reply => {
return res.json({
reply
})
})
})
.catch(err => next(err))

},
addLike: (req, res, next) => {
tweetServices.addLike(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
removeLike: (req, res, next) => {
tweetServices.removeLike(req, (err, data) => err ? next(err) : res.json({ status: 'success', data }))
},
}
module.exports = tweetController
Loading