# Node.js 每日作業
### 5/23 reset_pwd
```javascript=
router.post(
"/update-password",
isAuth,
handleErrorAsync(async (req, res, next) => {
const { password, confirmPassword } = req.body;
if (password !== confirmPassword) {
return next(appError("400", "您輸入的密碼不一致!", next));
}
newPassword = await bcrypt.hash(password, 12);
const user = await User.findByIdAndUpdate(req.user.id, {
password: newPassword
});
generateSendJWT(user, 200, res);
})
);
```
### 5/20 auth
```javascript=
const isAuth = handleErrorAsync(async (req, res, next) => {
let token;
const authorization = req.headers?.authorization;
if (authorization && authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return appError(401, '您尚未登入', next);
}
//check jwt is valid
const decodedToken = await new Promise((resolve, reject) => {
jwt.verify(token, process.env.JWT_SECRET, (error, payload) => {
error ? reject(appError(401, '未授權', next)) : resolve(payload);
});
});
const currentUser = await User.findById(decodedToken.id);
if (!currentUser.isLogin) appError(401, '請重新登入', next);
req.user = currentUser;
next();
});
```
### 5/19 jwt sign_in
```javascript=
router.post(
"/sign_in",
handleErrorAsync(async (req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return next(appError(400, "欄位資料有缺", next));
}
const user = await User.findOne({ email }).select('+password');
if (!user) return next(appError(400, "帳號或密碼錯誤", next));
const auth = await bcrypt.compare(password, user.password)
if (!auth) {
return next(appError(400, '帳號、密碼不正確', next))
}
generateSendJWT(newUser, 200, res);
})
);
```
### 5/17 jwt sign_up
```javascript=
//user.js
const { generateSendJWT } = require("../service/auth");
router.post(
"/sign_up",
handleErrorAsync(async (req, res, next) => {
const { email, password, userName } = req.body;
if (!email || !password || !userName) {
return next(appError(400, "欄位資料有缺", next));
}
if (password !== confirmPassword) {
return next(appError(400, "密碼不一致", next));
}
if (!validator.isLength(password, { min: 8 })) {
return next(appError(400, "密碼長度不得少於8碼", next));
}
if (!validator.isEmail(email)) {
return next(appError(400, "email格式錯誤", next));
}
const user = await User.findOne({ email });
if (user) return next(appError(400, "該email已被註冊", next));
const hashPassword = await bcrypt.hash(password, 12);
const newUser = await User.create({
email,
password: hashPassword,
userName,
});
generateSendJWT(newUser, 201, res);
})
);
//auth.js
const generateSendJWT = (user, statusCode, res) => {
const token = jwt.sign({ id: user._id}, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_DAY
})
user.password = undefined
res.status(statusCode).json({
status: "success",
user: {
token,
userName: user.userName
}
})
}
module.exports = { generateSendJWT }
```
### 5/16 validator
```javascript=
router.post('/sign_up', handleErrorAsync(async(req, res, next) =>{
let { email, password, confirmPassword, name } = req.body;
// 加入驗證,確保使用者註冊資料符合格式
if (!email || !password || !userName) {
return next(appError(400, "欄位資料有缺", next));
}
if (password !== confirmPassword) {
return next(appError(400, "密碼不一致", next));
}
if (!validator.isLength(password, { min: 8 })) {
return next(appError(400, "密碼長度不得少於8碼", next));
}
if (!validator.isEmail(email)) {
return next(appError(400, "email格式錯誤", next));
}
// 加密密碼
password = await bcrypt.hash(password, 12)
const newUser = await User.create({
email,
password,
name
});
res.status(200).json({
status: 'success',
data: newUser
});
}))
```
### 5/13 bcrypt.js
```javascript=
router.post('/sign_up', handleErrorAsync(async(req, res, next) =>{
let { email, password, confirmPassword, name } = req.body;
// 加密密碼
password = await bcrypt.hash(password, 12)
const newUser = await User.create({
email,
password,
name
});
res.status(200).json({
status: 'success',
data: newUser
});
}))
```
### 5/12 handleErrorAsync
```javascript=
//handleErrorAsync.js
const handleErrorAsync = (func) => {
return (req, res, next) => {
func(req, res, next).catch((error) => next(error));
};
};
module.exports = handleErrorAsync;
//post.js
router.post('/', handleErrorAsync( async(req, res, next) => {
const post = await Post.find();
res.status(200).json({
status: 'success',
results: post.length,
data: {
post
}
});
}
}))
```
### 5/11 development 及 production 環境變數指令、客製錯誤訊息
自行實作
### 5/10 自訂錯誤訊息
```javascript=
router.post('/', async(req, res, next) => {
try {
const data = req.body;
if (!data.content) {
// 將以下改為 appError 自訂錯誤回饋
return next(appError(400, 'content 欄位為必填', next))
}
const newPost = await Post.create(
{
user: data.user,
content: data.content,
tags: data.tags,
type:data.type
}
);
res.status(200).json({
status: 'success',
data: newPost
});
} catch (error) {
next(error);
}
})
```
### 5/9 - uncaughtException、unhandledRejection
自行實作
### 5/6 - http statusCode
```javascript=
Q:
1. Client 端對 Server 端發出 GET 請求,並且請求的 url 是 Server 端有的,將此資料回傳給 Client
2. Client 端對 Server 端發出 POST 請求並帶入產品資料,Server 端接收請求後成功新增一筆產品資料到資料庫中,並回傳此產品資料給 Client 端
3. Client 端對 Server 端發出 POST 請求並帶入產品資料,但帶入的資料格式錯誤,導致 Server 端無法正確處理資料及新增,因此回傳失敗的訊息給 Client
4. Client 端對 Server 端發出 GET 請求,但因 Server 端發生意外情況導致無法回傳需要的資源給 Client
5. Client 端對 Server 端發出 GET 請求,但 Server 端沒有與此 url 相符的路由,因此無法將請求資料回傳給 Client
6. Client 端對 Server 端發出 GET 請求,但請求的 url 需要有相關 Token 驗證,而Server 端未接收到 Token,驗證失敗,因此無法將請求資料回傳給 Client
Ans:
1. 200
2. 200
3. 400
4. 500
5. 404
6. 401 or 403
```
### 5/5 - Middleware
```javascript=
app.use((res, req, next)) => {
res.status(404).json({
status: 'error',
message: '無此頁面資訊'
})
})
app.use((req, res, next) => {
res.status(500).json({
status: 'error',
message: '系統錯誤,請洽系統管理員'
})
})
```
### 5/4 - sort()、limit()
```javascript=
router.get('/', async function(req, res, next) {
// 使用三元運算子判斷是否為 asc (由舊至新),若是則由舊至新排列,否則由新至舊排列
const timeSort = req.query.sort === 'asc' ? 'createdAt' : '-createdAt'
// 帶入網址列的參數
const limit = req.query.limit
const post = await Post.find().sort(timeSort).limit(limit);
res.status(200).json({
status: 'success',
data: {
post
}
});
})
```
### 5/3 - Mongoose - Populate
```javascript=
const mongoose = require('mongoose');
const authorSchema = new mongoose.Schema({
name: String,
introduction: String
}, { versionKey: false }
);
const bookSchema = new mongoose.Schema({
author : { type: mongoose.Schema.ObjectId, ref: 'Author' },
title: String
}, { versionKey: false }
);
const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);
```
```javascript=
Book.find({_id: id })
.populate({path: author, select: 'name'})
```
### 4/29 - req.params
```javascript=
//route
app.get('/posts/:id', async(req, res, next) => {
try{
const id = req.params.id;
const targetPost = await Post.findById(id)
if(!targetPost){
res.status(400).json({
status: 'fail',
message: '無此id'
});
}else{
res.status(200).json({
status: 'success',
post: targetPost
});
}
}catch (error){
res.status(400).json({
status: 'fail',
message: '請求失敗'
});
}
});
```
### 4/28 - req.body
```javascript=
//route
app.post('/posts', async(req, res, next) => {
try{
const { name, content } = req.body;
if(name === undefined || content === undefined){
res.status(400).json({
status: 'fail',
message: '參數有缺'
});
return
}
const newPost = awaitPost.create({name, content})
res.status(200).json({
status: 'success',
post: newPost
});
}catch (error){
res.status(400).json({
status: 'fail',
message: '新增失敗'
});
}
});
```
### 4/27 - req.query 篩選網址參數
請在 express 專案中,將以下 url 中的參數使用 req.query 取出,並回傳取出的參數(可自行建立 express 專案,先在 app.js 練習即可)
```javascript=
'http://localhost:3000/products?category=music&page=1' // 在 POSTMAN 發出 GET 請求
app.get('/products', function(req, res) {
const {category, page} = req.query;
/* 請在此填寫答案*/
res.status(200).json({
status: 'success',
data: {
category,
page
}
});
});
```
### 4/26 - 設計基本路由
```javascript=
const express = require('express');
const router = express.Router();
const User = require("../models/user.js");
router.post("/", async (req, res, next) => {
try{
const { nickName, gender } = req.body
if(nickName === undefined || gender === undefined){
res.status(400).json({
status: 'failed',
message:"參數有缺"
})
}
const newUser =await User.create({ nickName, gender });
res.status(200).json({
message: 'success',
user:newUser
})
}catch(error){
res.status(400).json({
status: 'failed',
message:'新增失敗'
})
}
})
router.patch("/:id", async (req, res, next) => {
try{
const id = req.params.id
const { nickName, gender } = req.body
if(nickName === undefined && gender === undefined){
res.status(400).json({
status: 'failed',
message:'參數有缺'
})
}
const updateUser = await User.findByIdAndUpdate(id, { nickName, gender }, {new:true});
if(updateUser){
res.status(200).json({
status: 'failed',
user: updateUser
})
}else{
res.status(400).json({
status: 'failed',
message: 'id 不存在'
})
}
}catch(error){
res.status(400).json({
status: 'failed',
message:'修改失敗'
})
}
})
```
### 4/25 - 設計基本路由
參考[最終作業設計稿](https://xd.adobe.com/view/c0763dbe-fc15-42e8-be0b-8956ed03e675-9525/grid)頁面,設計當使用者造訪以下頁面(`GET`)時的路由, response 可先回傳一段簡單的文字即可
- 登入
```javascript=
app.get("/login", (req, res) => {
res.send('歡迎來到登入頁')
})
```
- 註冊
```javascript=
app.get("/register", (req, res) => {
res.send('歡迎來到登入頁')
})
```
- 全體動態牆
```javascript=
app.get("/posts", (req, res) => {
res.send('歡迎來到登入頁')
})
```
- 個人牆
```javascript=
app.get("/posts/:id", (req, res) => {
res.send('歡迎來到登入頁')
})
```
- 個人追蹤名單
```javascript=
app.get("/follow/:id", (req, res) => {
res.send('歡迎來到登入頁')
})
```
### 4/22 - Express

```javascript=
//code
const express = require('express');
const app = express();
const port = 3000;
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
app.get('/', (req, res) => {
res.send('Hello World!')
})
```
### 4/21 - 拆分 Model
- models 資料夾 - drinks.js
```javascript
const mongoose = require('mongoose');
const drinkSchema = new mongoose.Schema({
product: {
type: String,
required: [true, '品名必填']
},
price: {
type: Number,
required: [true, '價錢必填']
},
sugar: {
type: String,
default: "全糖"
},
createdAt: {
type: Date,
default: Date.now,
select: false
}
},
{ versionKey: false }
)
...
const Drinks = mongoose.model('Drinks', postSchema)
module.exports = Drinks;
```
- server.js
```javascript
const Drinks = require('./models/drinks');
```
### 4/20 - Mongoose 修改 / 刪除
延續 Day6 - 7 每日任務,嘗試修改、刪除手搖飲 documents
可下載此[檔案](https://drive.google.com/drive/folders/1oRjCzs3OajeUXVroNO6QS7fNomO1hwZ0?usp=sharing)測試是否可正確更新、刪除單筆資料
1. 尋找一筆 document 並將 `ice` 改為 `去冰`,`sugar` 改為 `半糖`
```javascript
const Drink = mongoose.model("Drink", drinkSchema)
Room.findByIdAndUpdate("621e45063ff3c8af575a7498", {
ice:"去冰",
sugar: "半糖"
})
```
2. 以 ID 尋找一筆 document 並將其刪除
```javascript
Room.findByIdAndDelete("621e45063ff3c8af575a7498")
```
3. 刪除全部 documents
```javascript
Room.deleteMany({})
```
### 4/19 - Schema Options
延續 Day6 的每日任務,調整手搖飲的 Schema
- 加入 createdAt 欄位,並設定為隱藏欄位(不會顯示於前端)
- 隱藏 versionKey 欄位
提交範例
```javascript=
const drinkSchema = new mongoose.Schema({
product: {
type: String,
required: [true, '品名必填']
},
price: {
type: Number,
required: [true, '價錢必填']
},
sugar: {
type: String,
default: "全糖"
},
createdAt: {
type: Date,
default: Date.now,
select: false
}
},
{ versionKey: false }
)
```
### 4/18 - model、Mongoose 新增 / 查詢
延續前一天每日任務練習的手搖飲 Schema,建立名稱為 Drink 的 Model,並嘗試新增一筆 document
新增 document 內容如下
```
product: '鮮奶茶',
price: 55,
sugar: '微糖'
```
```javascript=
const drinkSchema = new mongoose.Schema({
product: {
type: String,
required: [true, '品名必填']
},
price: {
type: Number,
required: [true, '價錢必填']
},
sugar: {
type: String,
default: "全糖"
}},
)
const Drink = mongoose.model('Drink', drinkSchema);
const testDrink = new Drink({
product: '鮮奶茶',
price: 55,
sugar: '微糖'
});
testDrink.save()
.then(() => {console.log('新增資料成功')})
.catch((error) => {console.log(error)})
// 或另一種方式
Drink.create({
product: '鮮奶茶',
price: 55,
sugar: '微糖'
})
```
### 4/13 - MongoDB 基本操作: 修改、刪除
#### 題目(將答案寫在 HackMD 並提交至回報區)
若尚未做前一天的每日任務,需先建立一個 database(名稱可自定義),並建立一個 `students` collection
將以下資料新增至 `students` collection(若已做完前一天的每日任務,可繼續沿用已建立的 `students` collection)
```json
{
"studentName": "Riley Parker",
"group": "A",
"score": 83,
"isPaid": false
},
{
"studentName": "Brennan Miles",
"group": "C",
"score": 72,
"isPaid": false
},
{
"studentName": "Mia Diaz",
"group": "B",
"score": 98,
"isPaid": true
},
{
"studentName": "Caroline morris",
"group": "B",
"score": 55,
"isPaid": false
},
{
"studentName": "Beverly Stewart",
"group": "B",
"score": 60,
"isPaid": false
}
```
將答案依序列在自己的 HackMD 並將連結貼至回報區
```
範例:
1. ...
2. ...
3. ...
4. ...
```
1. 指定其中一個 `_id` ,並將該筆 document 的 `group` 改為 `D`
```javascript=
db.students.updateOne({"_id":ObjectId("621edf99a20aa7506a116f9a")},{$set:{group: "D"}})
```
2. 將 `group` 為 `B` 的多筆 document 的 `isPaid` 改為 `true`
```javascript=
db.students.updateMany({group: 'B'}, {$set:{isPaid: true}})
```
3. 將 `studentName` 包含關鍵字 `Brennan` 的 document 刪除
```javascript=
db.students.deleteOne({studentName:/Brennan/})
```
4. 將 `isPaid` 為 `true` 的多筆 的 document 刪除
```javascript=
db.students.deleteMany({isPaid:true})
```
### 4/12 - MongoDB 基本操作:新增、查詢
1. 依以下格式新增一筆 document 到 `students` collection
```json
{
"studentName": "Riley Parker",
"group": "A",
"score": 83,
"isPaid": false
}
```
範例:
```javascript=
db.students.insertOne({
"studentName": "Riley Parker",
"group": "A",
"score": 83,
"isPaid": false
})
```
2. 依以下格式一次新增多筆 document 到 `students` collection
```json
{
"studentName": "Brennan Miles",
"group": "C",
"score": 72,
"isPaid": false
},
{
"studentName": "Mia Diaz",
"group": "B",
"score": 98,
"isPaid": true
},
{
"studentName": "Caroline morris",
"group": "B",
"score": 55,
"isPaid": false
},
{
"studentName": "Beverly Stewart",
"group": "B",
"score": 60,
"isPaid": false
}
```
```javascript=
db.students.insertMany({
"studentName": "Brennan Miles",
"group": "C",
"score": 72,
"isPaid": false
},
{
"studentName": "Mia Diaz",
"group": "B",
"score": 98,
"isPaid": true
},
{
"studentName": "Caroline morris",
"group": "B",
"score": 55,
"isPaid": false
},
{
"studentName": "Beverly Stewart",
"group": "B",
"score": 60,
"isPaid": false
})
```
3. 查詢 `students` collection 中的所有資料
```javascript=
db.students.find()
```
4. 查詢 `students` collection 中符合 group 屬性為 B 的資料 `使用 { <field>: <value> } 設定符合的項目`
```javascript=
db.students.find({"group":"B"})
```
5. 查詢 `students` collection 中符合分數在 60 分以上的的資料
```javascript=
db.students.find({"score":{$gte:60}})
```
6. 查詢 `students` collection 中符合分數在 60 分以下**或是** group 為 B 的資料
```javascript=
db.students.find({$or:[{"score":{$lte:60}},{"group":"B"}]})
```