這是一個使用 Node.js + Express + MySQL 建立的 Simple Twitter 後端專案,部署於 Heroku,以 RESTFul API 滿足社群網站不同資料的互動需求,搭配 Simple-Twitter-Vue 前端專案,打造一個全方位的社群網站。
使用者可以使用以下帳號分別登入系統前台、後台。
role | account | password |
---|---|---|
admin | root | 12345678 |
user | RyanHuang | 12345678 |
- 使用者 CRUD
- 使用者可以註冊一個帳號
- 使用者可以新增一則推文
- 使用者可以回覆推文
- 使用者能對別人的推文按 Like/Unlike
- 使用者可以追蹤/取消追蹤其他使用者 (不能追蹤自己)
- 使用者可以編輯自己的帳號資料
- 使用者能編輯自己的名稱、介紹、大頭照和個人頁橫幅背景
- 使用者能在首頁瀏覽所有的推文
- 使用者點擊貼文方塊時,能查看該則貼文的詳情與回覆串
- 使用者可以瀏覽別的使用者的個人資料及推文
- 使用者能在首頁看見跟隨者 (followers) 數量排列前 10 的使用者推薦名單
- 任何登入使用者都可以瀏覽特定使用者的以下資料:推文、推文與回覆、跟隨中、跟隨者、喜歡的內容
- 管理者 CRUD
- 管理者可以瀏覽全站的 Tweet 清單
- 管理者可以瀏覽站內所有的使用者清單包含:使用者社群活躍數據,包括推文 (tweet) 數量、關注人數、跟隨者人數、推文被 like 的數量
- 管理者可以直接刪除任何人的推文
- 使用 bcryptjs 加密使用者密碼
- 整合 mocha、chai、sinon、sinon-chai 完成 Model 和 Request 的測試
- 加入 cors 實現跨網域連線
- 設定 dotenv 加入環境變數和隱藏敏感資訊
- 使用 faker 套件產生內容假資料
- 使用 multer 實作圖片上傳功能
- 整合 imgur-node-api 將圖片上傳至第三方平台
- 加入 validator 實作後端資料驗證
- sequelize - v4.44.4
- sequelize-cli - v5.5.1
- mysql2 - v1.7.0
- MySQL - v8.0.25
- MySQL workbench - v8.0.25
- passport - v0.4.1
- passport-jwt - v 4.0.0
- jsonwebtoken - v8.5.1
- socket.io - v4.1.3
- 請在終端機輸入
git clone https://github.com/wintersprouter/twitter-api-2020.git
cd twitter-api-2020
npm install (請參考 package.json)
- 建立.env
PORT='3000'
JWT_SECRET= xxx
IMGUR_CLIENT_ID= xxx
- 使用 MySQL Workbench 建立資料庫
- 需要與 config/config.json 一致
create database ac_twitter_workspace;
create database ac_twitter_workspace_test;
- 在終端機輸入以下指令,進行資料庫遷移、種子資料初始化
npx sequelize db:migrate
npx sequelize db:seed:all
- 在終端機輸入以下指令,啟動 swagger API 和 後端專案
npm run swagger-autogen
npm run dev
- 註: 在終端機輸入以下指令,可以清空種子資料
npx sequelize db:seed:undo:all
- 規劃資料庫 user、tweet、reply、like、followship models
- 撰寫 API 文件
- API 與功能開發
- 使用者與管理員登入 API
- 前台使用者註冊 API
- 追蹤一位使用者 API
- 取消追蹤一位使用者 API
- 瀏覽使用者的檔案 API
- 編輯自己的使用者檔案 API
- 編輯使用者帳戶資料 API
- 目前登入的使用者 API
- 全站追蹤者數量前10名的使用者名單 API
- 後台刪除一則貼文 API
- 後台瀏覽所有使用者 API
- 公開聊天室取得歷史訊息 API
- 使用者登入身分驗證設定
- sockiet.io 身分驗證設定
- 公開聊天室使用者上線、離線通知
- 公開聊天室線上使用者列表
- 撰寫 README.md
- 重構程式碼
- 規劃資料庫 message model
- 資料庫種子資料設定 user、tweet、reply、like、followship message seeders
- 撰寫 API 文件
- API 與功能開發
- 新增推文 API
- 瀏覽所有推文 API
- 瀏覽一則推文詳情 API
- 瀏覽一則推文詳情 API
- 回覆一則推文 API
- 瀏覽回覆 API
- 喜歡一則推文 API
- 取消喜歡一則推文 API
- 瀏覽使用者跟隨者 API
- 瀏覽使用者跟隨中的人 API
- 瀏覽使用者發過的所有推文 API
- 瀏覽使用者的所有回覆 API
- 瀏覽使用者所有點讚的推文 API
- 公開聊天室使用者訊息儲存
- 部屬 Heroku
- 撰寫 README.md
- 重構程式碼
http://localhost:3000/api-doc/
- 除了管理員、使用者登入和使用者註冊這 2 條路由外,其餘路由需在 header 的 Authorization 帶上"Bearer" + token (token可從登入時拿到)
- response 皆包含 http status code & message (說明成功狀態或是失敗原因)
POST /api/users
body | Type | Required |
---|---|---|
account | Srting | True |
name | String | True |
String | True | |
checkpassword | String | True |
password | String | True |
status code: 200
{
"status": "success",
"message": "@Lee sign up successfully.Please sign in."
}
status code: 400
{
"status": "error",
"message": [
"This email address is already being used.",
"This account is already being used."
]
}
status code: 400
{
"status": "error",
"message": [
"All fields are required!"
]
}
status code: 400
{
"status": "error",
"message": [
"Name can not be longer than 50 characters.",
"Account can not be longer than 20 characters.",
"example.com is not a valid email address.",
"Password does not meet the required length.",
"The password and confirmation do not match.Please retype them."
]
}
POST /api/users/signin
Body | Type | Required |
---|---|---|
account | Srting | True |
password | String | True |
status code: 200
{
"status": "success",
"message": "Sign in successfully.",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NywiaWF0IjoxNjI1OTg5NzYzfQ.1eaLL0Qn19CMHeO33R93S80w3aHEz9LKhuWsso69W7w",
"user": {
"id": 7,
"account": "Lee",
"email": "lee@example.com",
"name": "Lee",
"avatar": "https://www.seekpng.com/png/full/114-1149972_avatar-free-png-image-avatar-png.png",
"cover": "https://www.seekpng.com/png/full/153-1536586_free-facebook-cover-image-transparent-png-facebook-cover.png",
"role": "user"
}
}
status code: 422
{
"status": "error",
"message": [
"All fields are required!"
]
}
status code: 401
{
"status": "error",
"message": [
"That account is not registered!"
]
}
status code: 401
{
"status": "error",
"message": [
"Account or Password incorrect."
]
}
POST /api/tweets
Body | Type | Required |
---|---|---|
description | Srting | True |
status code: 200
{
"status": "success",
"message": "The tweet was successfully created."
}
status code: 400
{
"status": "error",
"message": [
"Please input tweet."
]
}
status code: 409
{
"status": "error",
"message": [
"Tweet can't be more than 140 words."
]
}
GET /api/tweets
status code: 200
[
{
"id": 30,
"UserId": 3,
"description": "dicta",
"createdAt": "2021-07-01T22:57:24.000Z",
"account": "AaronWang",
"name": "Aaron",
"avatar": "https://i.pravatar.cc/150?img=56",
"likedCount": 0,
"repliedCount": 3,
"isLike": false
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find this tweets in db."
]
}
GET /api/tweets/:tweet_id
tweet_id:欲瀏覽的推文 id
status code: 200
{
"status": "success",
"message": "Get the tweet successfully",
"id": 1,
"UserId": 1,
"description": "Aut enim reiciendis dicta quo ducimus tempora illum soluta. Eligendi nobis molestias hic. Numquam eos dignissimos doloribus nisi minus conse",
"createdAt": "2021-03-24T03:10:18.000Z",
"account": "RyanHuang",
"name": "Ryan",
"avatar": "https://i.pravatar.cc/150?img=68",
"likedCount": 2,
"repliedCount": 3,
"isLike": true
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this tweet in db."
]
}
POST /api/tweets/:tweet_id/replies
Body | Type | Required |
---|---|---|
comment | Srting | True |
tweet_id:欲回覆的推文 id
status code: 200
{
"status": "success",
"message": "You replied @${repliedTweetAuthor}'s tweet successfully."
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this tweet in db."
]
}
status code: 400
{
"status": "error",
"message": [
"Please input comment."
]
}
status code: 409
{
"status": "error",
"message": [
"Comment can\'t be more than 50 words."
]
}
GET /api/tweets/:tweet_id/replies
tweet_id:瀏覽回覆的推文 id
status code: 200
[
{
"id": 2,
"UserId": 3,
"TweetId": 1,
"tweetAuthorAccount": "RyanHuang",
"comment": "Suscipit at rerum excepturi id ullam laboriosam at",
"createdAt": "2021-06-22T17:24:32.000Z",
"commentAccount": "AaronWang",
"name": "Aaron",
"avatar": "https://i.pravatar.cc/150?img=56"
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find any replies in db."
]
}
POST /api/tweets/:id/like
id:欲點like的推文 id
status code: 200
{
"status": "success",
"message": "You liked @${likedTweetAuthor}'s tweet successfully."
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this tweet in db."
]
}
status code: 400
{
"status": "error",
"message": [
"You already liked this tweet."
]
}
POST /api/tweets/:id/unlike
id:欲取消like的推文 id
status code: 200
{
"status": "success",
"message": "You unliked ${unlikedTweetAuthor}'s tweet successfully."
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this tweet in db."
]
}
status code: 400
{
"status": "error",
"message": [
"You never like this tweet before."
]
}
POST /api/followships
Body | Descriotion |
---|---|
followingId | 欲追蹤的使用者id |
status code: 200
{
"status": "success",
"message": "You followed @${followingUser.account} successfully."
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this followingId or followerId."
]
}
status code: 403
{
"status": "error",
"message": [
"You cannot follow yourself."
]
}
status code: 409
{
"status": "error",
"message": [
"You already followed @${followingUser.account}"
]
}
DELETE /api/followships/:followingId
followingId:欲取消追蹤的使用者 id
status code: 200
{
"status": "success",
"message": "Unfollowed @${followingUser.account} successfully."
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this followingId or followerId."
]
}
status code: 403
{
"status": "error",
"message": [
"You cannot unfollow yourself."
]
}
status code: 409
{
"status": "error",
"message": [
"You didn't followed @${unfollowingUser.account} before."
]
}
POST /api/users/:id
id:欲瀏覽的使用者 id
status code: 200
{
"status": "success",
"message": "Get @BeatricePai's profile successfully.",
"id": 4,
"name": "Beatrice",
"account": "BeatricePai",
"email": "betrice@example.com",
"avatar": "https://i.pravatar.cc/150?img=28",
"cover": "https://loremflickr.com/660/240/paris/?lock=95.94581210965639",
"introduction": "Soluta iusto nihil ut. Ipsam alias nesciunt voluptatem.,
"tweetCount": 10,
"followerCount": 2,
"followingCount": 1,
"isFollowed": true
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any user in db."
]
}
PUT /api/users/:id
id:目前登入的使用者 id
Body | Type | Required |
---|---|---|
name | String | True |
introduction | String | False |
avator | String | False |
cover | String | False |
status code: 200
{
"status": "success",
"message": "Update ${name}'s profile successfully."
}
status code: 401
{
"status": "error",
"message": [
"Permission denied."
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any user in db."
]
}
status code: 400
{
"status": "error",
"message": [
"Name is required."
]
}
status code: 400
{
"status": "error",
"message": [
"Name can not be longer than 50 characters."
"Introduction can not be longer than 160 characters."
]
}
status code: 400
{
"status": "error",
"message": [
" Image type of file should be .jpg, .jpeg, .png ."
]
}
PUT /api/users/:id/account
id:目前登入的使用者id
Body | Type | Required |
---|---|---|
account | Srting | True |
name | String | True |
String | True | |
password | String | True |
status code: 200
{
"status": "success",
"message": "@${account} Update account information successfully."
}
status code: 401
{
"status": "error",
"message": [
"Permission denied."
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any user in db."
]
}
status code: 400
{
"status": "error",
"message": [
"All fields are required!"
]
}
status code: 400
{
"status": "error",
"message": [
"Name can not be longer than 50 characters.",
"Account can not be longer than 20 characters.",
"example.com is not a valid email address.",
"Password does not meet the required length.",
"The password and confirmation do not match.Please retype them."
]
}
status code: 400
{
"status": "error",
"message": [
"This email address is already being used.",
"This account is already being used."
]
}
GET /api/users/:id/followers
id:欲瀏覽的使用者id
status code: 200
[
{
"followerId": 2,
"account": "LyviaLee",
"name": "Lyvia",
"avatar": "https://i.pravatar.cc/150?img=29",
"introduction": "Illo ab sed quos maxime adipisci est.",
"followshipCreatedAt": "2021-03-17T13:07:42.000Z",
"isFollowed": false
}
...
]
status code: 200
{
"message": [
"@${user.account} has no follower."
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any user in db."
]
}
GET /api/users/:id/followings
id:欲瀏覽的使用者id
status code: 200
[
{
"followerId": 2,
"account": "LyviaLee",
"name": "Lyvia",
"avatar": "https://i.pravatar.cc/150?img=29",
"introduction": "Illo ab sed quos maxime adipisci est.\nFugiat facere dolores quis quidem impedit id.",
"followshipCreatedAt": "2021-03-17T13:07:42.000Z",
"isFollowed": false
}
...
]
status code: 200
{
"message": [
"@${user.account} has no following."
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find this user in db.",
]
}
GET /api/users/:id/tweets
id:欲瀏覽的使用者id
status code: 200
[
{
"id": 5,
"UserId": 1,
"description": "labore",
"createdAt": "2021-06-19T21:05:51.000Z",
"account": "RyanHuang",
"name": "123",
"avatar": "https://i.pravatar.cc/150?img=68",
"likedCount": 0,
"repliedCount": 3,
"isLike": false
},
{
"id": 9,
"UserId": 1,
"description": "Vel est ut ea amet mollitia.",
"createdAt": "2021-05-03T05:00:31.000Z",
"account": "RyanHuang",
"name": "123",
"avatar": "https://i.pravatar.cc/150?img=68",
"likedCount": 1,
"repliedCount": 3,
"isLike": false
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find this user in db.",
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any tweets in db.",
]
}
GET /api/users/:id/replied_tweets
id:欲瀏覽的使用者id
[
{
"id": 25,
"UserId": 1,
"TweetId": 9,
"tweetAuthorAccount": "RyanHuang",
"comment": "praesentium cumque perspiciatis",
"createdAt": "2021-07-04T14:32:25.000Z",
"commentAccount": "RyanHuang",
"name": "Ryan",
"avatar": "https://i.pravatar.cc/150?img=68"
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find this user in db.",
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any replies in db.",
]
}
GET /api/users/:id/likes
id:欲瀏覽的使用者id
status code: 200
[
{
"id": 12,
"UserId": 1,
"TweetId": 28,
"likeCreatedAt": "2021-03-23T17:47:34.000Z",
"account": "AaronWang",
"name": "Aaron",
"avatar": "https://i.pravatar.cc/150?img=56",
"description": "fugiat accusantium vitae",
"tweetCreatedAt": "2021-04-22T20:51:47.000Z",
"likedCount": 1,
"repliedCount": 3,
"isLike": true
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find this user in db.",
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any liked tweets in db.",
]
}
GET /api/users/current
status code: 200
{
"id": 1,
"name": "Ryan",
"account": "RyanHuang",
"email": "ryan@example.com",
"avatar": "https://i.pravatar.cc/150?img=68",
"role": "user",
"cover": "https://loremflickr.com/660/240/paris/?lock=37.08013914092159",
"introduction": "Et odio eaque.\nQuae illum nemo."
}
GET /api/users
status code: 200
{
"status": "success",
"message": "Get top ten users successfully",
"users": [
{
"id": 3,
"name": "Aaron",
"avatar": "https://i.pravatar.cc/150?img=56",
"account": "AaronWang",
"followerCount": 2,
"isFollowed": true
},
{
"id": 4,
"name": "Beatrice",
"avatar": "https://i.pravatar.cc/150?img=28",
"account": "BeatricePai",
"followerCount": 1,
"isFollowed": true
},
...
]
}
status code: 404
{
"status": "error",
"message": [
"Cannot find any user in db."
]
}
DELETE /api/admin/tweets/:id
id:欲刪除的推文id
status code: 200
{
"status": "success",
"message": [
"@${tweetAuthor}'s tweet has been deleted!"
]
}
status code: 401
{
"status": "error",
"message": [
"This tweet doesn't exist!"
]
}
GET /api/admin/users
status code: 200
[
{
"id": 1,
"account": "RyanHuang",
"name": "Ryan",
"avatar": "https://i.pravatar.cc/150?img=68",
"cover": "https://loremflickr.com/660/240/paris/?lock=62.67199844521949",
"tweetCount": 10,
"likedCount": 5,
"followingCount": 0,
"followerCount": 1
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find any users in db.",
]
}
Get /api/chat
status code: 200
[
{
"id": 4,
"UserId": 5,
"content": "Dicta eos et excepturi. Conseq",
"createdAt": "2021-03-10T14:19:33.000Z",
"account": "TimChien",
"name": "Tim",
"avatar": "https://i.pravatar.cc/150?img=60"
},
...
]
status code: 404
{
"status": "error",
"message": [
"Cannot find any historyMessage in db.",
]
}