---
# System prepended metadata

title: "\U0001F3C5 Day 39 - 實作新增貼文留言功能"
tags: [Node.js 直播班 - 2024 春季班]

---

# 🏅 Day 39 - 實作新增貼文留言功能

練習整合運用先前提到的技巧：
- router 
- Moogoose create()
- req.params、req.body
- JWT middleware

實作出設計稿 `3-2.全體動態牆-有留言` 頁面的「新增貼文留言功能」

### 流程
當使用者點選「留言」按鈕時，可以將使用者輸入的留言新增進留言資料中

**實作前準備**
由於無法預期單則貼文的留言數量，因此留言的部分會另外使用 collection 存放
`models/commentsModel.js`
```javascript
const commentSchema = new mongoose.Schema(
  {
    comment: {
      type: String,
      required: [true, 'comment can not be empty!']
    },
    createdAt: {
      type: Date,
      default: Date.now
    },
    user: {
      type: mongoose.Schema.ObjectId,
      ref: 'User',
      require: ['true', 'user must belong to a post.']
    },
    post: {
      type: mongoose.Schema.ObjectId,
      ref: 'Post',
      require: ['true', 'comment must belong to a post.'],
    }
  }, { versionKey: false }
);
```
由於 Comments 存放在另一個 collection，因此若取得貼文時需要顯示出相關留言串，需另外在 Post Schema 中加入**虛擬欄位**
而此虛擬欄位 comments 會關聯 Comment Model 的 post 欄位及 Post Model 的 _id 欄位，找出所有該貼文 ID 的留言
```javascript=
postSchema.virtual('comments', {
  ref: 'Comment',
  foreignField: 'post',
  localField: '_id'
});
```
需注意當 document 被轉為 JSON 或 Object 資料時，是不包含虛擬欄位的，因此需記得在貼文 Schema 加入 
```javascript=
toJSON: { virtuals: true },
toObject: { virtuals: true },
```

整體 Schema 設定可參考範例程式碼 [commentsModel.js](https://github.com/gonsakon/express-week4-sample/blob/week8/models/commentsModel.js)、[postsModel.js](https://github.com/gonsakon/express-week4-sample/blob/week8/models/postsModel.js)


#### 參考資源
- [Mongoose v6.3.4: Mongoose Tutorials: Mongoose Virtuals](https://mongoosejs.com/docs/tutorials/virtuals.html)
- [Mongoose Middleware - Pre](https://mongoosejs.com/docs/middleware.html#pre)


### 開始實作
**設計 POST `/posts/:id/comment` 路由**
- 需登入通過 JWT 驗證才能請求
- 新增傳入的留言，以及留言者 user ID、貼文 ID 新增至 comments collection 中
- 需回傳使用者新增的留言

依照上方需求完整以下程式碼，補上 `...` 的部分

`app.js`
```javascript=
const postsRouter = require('./routes/posts');
app.use('/posts', postsRouter);
```

`routes/posts.js`
```javascript=
router....('...', ..., handleErrorAsync(async (req, res, next) => {
  const user = req.user.id;
  const post = req....;
  const { comment } = req....;
  const newComment = await Comment....({
    user,
    post,
    comment
  });
  res.status(201).json({
    status: 'success',
    data: {
      comments: ...
    }
  });
}))
```

## 回報流程

將答案寫在 CodePen 並複製 CodePen 連結貼至底下回報就算完成了喔! 
解答位置請參考下圖（需打開程式碼的部分觀看）

![](https://i.imgur.com/vftL5i0.png)

<!-- 解答：  

`app.js`
```javascript=
const postsRouter = require('./routes/posts');
app.use('/posts', postsRouter);
```


`routes/posts.js`
```javascript=
router.post('/:id/comment', isAuth, handleErrorAsync(async (req, res, next) => {
  const user = req.user.id;
  const post = req.params.id;
  const { comment } = req.body;
  const newComment = await Comment.create({
    post,
    user,
    comment
  });
  res.status(201).json({
    status: 'success',
    data: {
      comments: newComment
    }
  });
}))
```

-->




回報區
---
<!-- 
將答案貼至下方表格內，格式：
| Discord 暱稱 | [CodePen](連結) |
-->

|    Discord    |   CodePen ／ 答案  |
|:-------------:|:-----------------:|
| xxx | [CodePen]() |
| william威良 | [CodePen](https://codepen.io/snowman12320/pen/WNBoExP) |
| Chia Pin | [CodePen](https://codepen.io/joker-cat/pen/mdYOMaz) |
| wei | [CodePen](https://hackmd.io/@xu7yoa5cSsqaron7h9XhUw/r1pqeZXER)|
| 苡安 | [hackmd](https://hackmd.io/@L7K9-66lSeagS28AP0_GjQ/Byzs6eV40)|
| runweiting | [CodePen](https://codepen.io/weiting14/pen/OJYWVVG)|
| jenny7532 | [CodePen](https://codepen.io/wei-chen-wu/pen/ExzWVwO)|
| mei | [CodePen](https://codepen.io/l_umei/pen/WNBpXvx)|
| Hank | [CodePen](https://codepen.io/tw1720/pen/mdYdwNW)|


