# NTUEE+ documentation
<small>[回MD首頁](https://hackmd.io/CSNbja7XTYCYquYxgq4Xow)</small>
## 起源
>試著回想18歲的自己,未來的人脈往往成為促使我們選擇台大電機的原因,然而曾經我們引以為傲的人脈資產,現在卻如此凋零。
一瞥世界上知名大學,他們都擁有一個共通點:人脈網絡。哈佛大學的老爺爺願意為了甫錄取的學弟妹提點長談,史丹佛大學的前輩也不遺餘力提拔後進。相比之下,我們認為系上一直缺乏專屬平台供系友建立緊密的網路,遂使人脈日益薄弱。
近年創立的NTUEE Chain已經輔導眾多學生申請上國外一流大學,我們更希望延續EE Chain的初衷,讓這份互相傳承聯絡的心拓展到所有系友。繼承著B03~B06學長姐們的意志,我們希望這個聯絡網能成為一個整合式的社群網路,讓NTUEErs聚在一起;秉持著恢復人脈網的精神,讓NTUEE能在世界上有更大的影響力;建立一個連結電機系的共同回憶,讓系友們有專屬的家!
:::info
~~這並不是一個網頁養成班,沒辦法讓所有人把寫網頁的所有技能練習套精熟,主要學習內容是依照本網站的需求進行延伸,會從最基本的寫起,並漸漸加深加廣,目標寫出一個完整、讓人驚艷的網頁\^_^~~
:::
---
## [背景知識](https://drive.google.com/drive/folders/1F_NW1_4ykXiAwF4cIsI16QSROVeAQVYy?usp=sharing)
### 環境安裝
* 安裝列表
* [nodejs v12](https://nodejs.org/zh-tw/download/)
* [git v2](https://git-scm.com/)
* 如果還沒註冊git帳號的記得去註冊
* [vscode](https://code.visualstudio.com/)
* vscode extensions
* npm
* JavaScript (ES6) code snippets
* vscode extensions(recommended)
* GitLens
* Bracket Pair Colorizer
* [Markdown Preview Github Styling](https://ithelp.ithome.com.tw/articles/10190508)
:::warning
如果有漏掉的隨時回報
:::
* 程式運作
* 選擇要存放檔案的資料夾
* ctrl+\`打開terminal,輸入
```bash
$ git clone https://github.com/chinyi0523/NTUEE_Plus_website.git
$ cd NTUEE_Plus_website
$ npm install
$ npm run install-client
$ npm start
```
* 打開[http://localhost:1993](http://localhost:1993),應該會看到EE+的網頁
### git
一樣在vscode的terminal即可
* 初始設定,在repo上才知道誰是誰
```bash
# 輸入使用者名稱
$ git config --global user.name "YourName"
# 輸入帳號email
$ git config --global user.email "YourMail@gmail.com"
# 查看使用者名稱
$ cat ~/.gitconfig
```
* git status
* 用git add \<filename>站存
* 用git commit -m 'commit message'存檔


* 檔案管理(local)
* add:把特定檔案加到暫存區
```bash
# 暫存所有檔案(我都是用這個)
$ git add . 或 git add -a 或 git add --all
# 暫存特定檔案(善用*,例如*.txt)
$ git add <filename>
# 暫存所有modified的檔案(無視untrack),但建議用.gitignore
$ git add -u
```
* .gitignore:裡面的檔名會被```git add .```無視
* commit:存檔,commit message說明這次存檔變更了甚麼,建議英文寫(我也不知道為啥)
```bash
# 存檔,'commit message'換成你的簡述
$ git commit -m 'commit message'
#如果覺得commit -m太長,可以在config設定縮寫cm
$ git config --global alias.cm 'commit -m' #以後打git cm取代git commmit -m
```
* 刪除和改名
```bash
# 變成untrack file(建議)
$ git rm <filename> --cached
# 直接從電腦刪除
$ git rm <filename>
# 改檔名(不用重新add)
$ git mv <oldname> <newname>
```
* 檔案上傳(local↔remote)
remote為遠端程式庫,當local的程式有更新就可以上傳到remote上,預設只有一個版本叫做origin(也就是clone的來源,github的網址),也可以自己[設定](https://git-scm.com/book/zh-tw/v2/Git-%E5%9F%BA%E7%A4%8E-%E8%88%87%E9%81%A0%E7%AB%AF%E5%8D%94%E5%90%8C%E5%B7%A5%E4%BD%9C)其他遠端版本的連結
```bash
# 從origin更新程式
$ git pull
# 把local的更動上傳到origin
$ git push
```
* 分支branch
善用分支讓大家的程式不打架

* 分支管理
```bash
#看目前所在的branch和local所有的branch
$ git branch
$ git branch -a #茶和local+remote所有分支
$ git checkout <branchname> #切換到其他branch
#新增branch並切換過去,注意!他會以當前branch為根源
$ git checkout -b <branchname>
```
* 與遠端互動
```bash
# 抓遠端master
$ git pull origin master
#把local當前的branch發到remote的branch上
$ git push -u origin <branch name>
# 之後在這個branch pull、push都是對origin branch做事
```
* merge(重要!
1. 與master merge的規則:
在自己的branch做事,確保城市沒炸掉後才可以merge。
把自己的branch發到remote,發pr。
2. 小組功能的merge:
之後可能會讓幾個人一起開發一個功能,所以可能有一個branch開了3個小branch的情況,例如:
Recruitment下有Recruitment-backend、Recruitment-frontend。
方法1:git merge

```bash
# 如果後端完成事情要要merge
$ git checkout Recruitment
$ git merge Recruitment-backend
# 如果前端想要拿後端merge到Recruitment的資料
$ git checkout Recruitment-frontend
$ git merge Recruitment
```
方法2:rebase

通常用在自己的branch上,特點是保持分支樹乾淨(最終會merge成一條),
例如Recruitment-backend開了個子branch <testAwait>
```bash
$ git checkout Recruitment-backend
$ git rebase testAwait
```
### 網站架構
#### front-end
##### react
###### state
##### axios
#### back-end
##### 檔案架構
```
├── backend/routes/
├── api.js
### doc相關 ###
├── apidoc.json #生成readme的rule
├── README.MD #在/backend跑yarn doc生成
├── docTemplate/ #README的template
### middleware相關 ###
├── error/
├── index.js
├── ErrorHandler #throw它可以指定狀態碼(404,500,...)
├── handleError #api.js最底下呼叫,處理所有error
├── dbCatch #mongoose相關的error handling
├── middleware/
├── mail/ #寄信
├── validation/ #判斷req格式
├── fileProcess.js #把formdata塞進req.body
### 主要程式碼 ###
├── Schemas #資料庫相關
### 特殊 ###
├── db.js #與資料庫連線的基本設定
├── preload.js #docker建好後在/跑yarn reset-db
├── query.js #把req變成mongoose的query
### collections ###
├── ...
├── srcs #api相關
├── in/
├── abroadinfo #留學資訊
├── account
├── auth/ #管理員相關的帳號設定
├── ...
├── auth #用session檢查權限
├── isAuth.js
├── isUser.js
├── career #就是recruitment
├── column
├── profile_new
├── recommendation
├── study #留學配對
├── out/
├── account #註冊登入那些的
├── dashboard
├── forget #忘記密碼
```
##### nodejs
obj小教學:
```js
const obj1 = {a:1,b:2}
const {a,b} = obj1//a=1,b=2
const c = 3
const d=4
//{c:3,d:4}
const obj2 = {c,d}
```
##### [express](https://expressjs.com/zh-tw/)
###### intro
Express.js是後端Node.js的開發框架之一,框架有點像是一個工具包,開發者能夠使用 npm(Node Package Manager) 安裝所需要的工具包到專案資料夾,輕鬆完成專案的使用環境設定,讓你專注在程式碼的撰寫與實際運行上。
```js
//index.js
//開啟app應用程式,套用express框架
const express = require('express');
const app = express();
...
//設定前後端路由(下面介紹)
app.use("/api", require("./backend/routes/api"));
app.use(express.static('./client/dist'));
...
//在http://localhost:1993監聽此應用程式
app.listen(process.env.PORT||1993,function(){
console.log('server connect');
console.log('port name: ',process.env.PORT||1993);
})
```
###### [route](https://expressjs.com/zh-tw/guide/routing.html)
路由是指判斷應用程式如何回應用戶端對特定端點的要求,而這個特定端點是一個 URI(或路徑)與一個特定的 HTTP 要求方法(GET、POST 等)。
每一個路由可以有一或多個處理程式函式,當路由相符時,就會執行這些函式。
路由定義的結構如下:
```js
app.METHOD(PATH, HANDLER);
//METHOD 是 HTTP 要求方法。
//PATH 是伺服器上的路徑。
//HANDLER 是當路由相符時要執行的函數,或稱中介軟體
```
如果想要用一個path管理很多個路由,可以用express.Router,可以想成是迷你應用程式。
例如後端的程式由api.js控管,並輸出成一個router在/api下運作。
```js
//routes/api.js
const express = require("express");
const router = express.Router();
router.post(url1,function1);
router.post(url2,function2);
...
module.exports = router;
```
```js
//index.js
...
app.use("/api", require("./routes/api"));
...
```
###### 中介軟體
中介軟體函數是寫在route中的函式,他的input固定為「要求物件 (req)」、「回應物件 (res)」 和「執行下一個中介軟體(next)」,常見的形式為:
```js
router.post(url,function(req, res, next){
//do something
next()
},function(req,res,next){})
```
* 要求物件[req](https://expressjs.com/zh-tw/4x/api.html#req) (Object)
它是http request,內含呼叫這個url時夾帶的參數。常用的有:
* req.body (Object):
前端發post時夾帶的data
```js
//client
axios.post(url,{account:"b07901029",username:"huho"},config)
->
//backend
req.body.account //"b07901029"
```
* req.query (Object):
前端發get時夾帶的data
```js
//client
GET /url?account=b07901029&username=均府
->
//backend
req.query.account //"b07901029"
```
* req.protocol+"://"+req.get('host')+"/someURL"
拿到client的網址,考慮到上架後的網址不再是localhost,要這樣才能拿到最正確的網址,當然網路上也一定查的到其他方法。
* 回應物件[res](https://expressjs.com/zh-tw/4x/api.html#req) (Object)
當獲得http request後,express就會發出http response。
* res.header
設定http參數
* res.status(httpCode).send(obj or string)
回傳資料給前端。
[http code](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81)
成功通常是用200或201
失敗通常是4xx或5xx
* res.status(httpCode).end()
和send差不多,但不回傳資料。
* 下一個中介軟體(next) (function)
在route中可以執行很多個funciton,用
```js
router.METHOD(url,
function(req,res,next){},
function(req,res,next){},...)
```
預設會執行第一個函式,當第一個函式呼叫了next(),就會執行第二個函數。
一般來說如果router呼叫同一個url兩次,則只有第一個(在檔案比較前面的)會被執行,但如果呼叫next('route'),就會切出這個route執行第二個route
```js
router.post('/login',function(req,res,next){
next('route');
},function(req,res,next){
console.log('我不會被執行')
})
router.post('/login',function(req,res,next){
console.log('我會被執行')
})
```
善用中介軟體可以把許多路由都用到的function寫在外面呼叫,讓程式更簡潔。
<big>**一些express常見的應用**</big>
* router.use(function)
強制執行所有路由都要經過他(好像要注意priority?)
通常會用來設定參數(例如index.js中的session或urlencoded或res header),或執行一些大家統一要用的參數。
* session
* urlencoded、json
* Auth
驗證使用者是否有登入
```js
//api.js
const Auth = require('/in/Auth');
axios.post(url,Auth,...);
//Auth.js
module.export = funciton(req,res,next){
if(req.session){
next()
}else{
//開發階段這裡呼叫next();
res.status(403).send({description:"請登入"});
}
}
```
* [multer](https://riptutorial.com/zh-TW/node-js/example/14210/%E4%BD%BF%E7%94%A8multer%E4%B8%8A%E5%82%B3%E5%96%AE%E5%80%8B%E6%96%87%E4%BB%B6)
專門處理檔案上傳,在/register、/chVisual、/getImg有應用在照片處理
不想深入研究的話就直接套用:
1. 存單張照片
```js
//client
let data = new FormData();
data.append('avatar',this.state.file);
//用append設定其他要post的資料,下面介紹更多關於formData的注意事項
const config = {headers:
{'content-type':'multipart/form-data'}
};
axios.post(url,data,config);
->
//backend
const parseFile = require('./middleware/multer.js');
router.post(url,parseFile('avatar'),...);
//ImgGet裡輸入要讀取的檔案的key
//之後的function就可以在req.file裡拿到{buffer,mimetype(img/png之類的)}
```
* formData注意事項
```js
//frontend
//要傳object時請用json
let data = new FormData();
const obj = {data:"b07901029",show:true}
data.append("account",JSON.stringify(obj))
//要傳array key請加[]
const arr = ["a","b","c"]
arr.forEach(item=>{
//如果item是obj請參考上面解法
data.append("fakeArr[]",item)
})
->
//backend
const {data,show} = req.body["account"] //{"b07901029",true}
//用forEach拿array
req.body["fakeArr"].forEach(item=>{
console.log(item);
//"a"\n"b"\n"c"
})
```
2. 若要讀取多個檔案請仿照middleware/multer.js,但upload.single改成upload.array。
* 可以參考study/runMatch/parseExcel
3. 拿單張照片
```js
//backend
//這裡req.file應該變成我們存放mimetype和buffer的地方
const prefix="data:"+req.file.mimetype+";base64,"
const img = new Buffer(req.file.buffer, 'binary').toString('base64');
const userimage = prefix+img;
res.send({userimage})
->
//client
//拿到的userimage用this.setState({userimage}),然後可直接設成<img>的value:
<img value={this.state.userimage}>
```
* [validation](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-%E6%8A%8A%E7%8E%A9-express-validator-%E5%9C%A8%E4%BC%BA%E6%9C%8D%E5%99%A8%E7%AB%AF%E5%81%9A%E8%A1%A8%E5%96%AE%E9%A9%97%E8%AD%89-797342aab2d3)
在執行主程式前用express-validator驗證資料格式,目的是不要讓使用者傳奇怪的資料到後端
```js
//基本架構:
const {check} = require('express-validator')
router.post(url,[check(key1),check(key2),...],authController,function,...)
```
* express-validator中有三種常用函式:body、cookie、check,分別會檢驗req.body、req.cookies、req.{body,cookies,header,param,query}下指定key對應的value有沒有符合格式。
* 若沒有符合格式,消息會被存在req中並呼叫下一個函式:authController,express-validator下還有validationResult這個函式,用validationResult(req)可以抓到錯誤並回傳res.status(400)。
* 以下是我們的版本(以login為例),require validation,然後指定要檢查的欄位
```js
const valid = require('../../../middleware/validation')
const rules = ['account', 'password']
module.exports = [valid(rules), asyncHandler(login)]
```
*
##### [mongoDB and mongoose](https://developer.mozilla.org/zh-TW/docs/Learn/Server-side/Express_Nodejs/mongoose)
###### intro
mongoDB是一個文件導向的資料庫(database),一個資料庫底下可以有多個集合(collections),每個集合下有多個文檔(documentions)。
以我們的專案為例,我們的database存放在mongoDB online上,名叫eeplus。當中有以下collections:
```
user_logins
user_visuals
columns
...
```
而user_logins有數個documentation,格式類似json
```
{_id:ObjectID(b6e5rb48e6), account:"b07901029", userpsw:"f2hoifpew"},
{_id:ObjectID(ber561r65b), account:"b07901014", userpsw:"vewoivnro"},
...
```
mongoose是nodejs中用來與mongoDB溝通的套件。
* 基本設定:連線與監聽
```js
//routes/Schema/db.js
const mongoose = require('mongoose');
mongoose.connect(DB_URL);
//form like mongodb://<user>:<psw>@host:port/dbname
const db = mongoose.connection(); //背景監聽
db.on('disconnected', function(){
console.log('disconnected!');
});
```
* 模型(model)與綱要(schema)
一個model對應到mongoDB中的一個collection,透過schema定義格式。我們可以透過model進行doc的創建、查找、更新、刪除。
```js
const mongoose = require("./db")
const Schema = mongoose.Schema;
//在此設定model的type、驗證、虛擬屬性(我沒用過,感覺很好玩!)
const User_login_Schema = new Schema({
//透過:TYPE設定類型
username: String,
//也可以規定資料的內容,例如enum:['dog','cat']限定內容選項
account: {type:String, require:True},
...,
//可以設定多層的資料,也可以設定[]
img: {
data: { type: Buffer },
contentType: { type: String }
}
});
//把schema編譯成model,之後就可以對model進行操作
//之後save的檔案會存在user_logins(好像會自動加s)這個collection中
module.exports = mongoose.model("User_login", User_login_Schema);
```
* schema tips
1. [type and some attribute](https://mongoosejs.com/docs/schematypes.html)
2. population:{_id(docID),ref(collectionName)},與其他collecton連結
3. instance method(doc's function)。針對單一doc做事,用this拿到該doc的屬性。
```js
// define a schema
const animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
return mongoose.model('Animal').find({ type: this.type }, cb);
};
```
4. static(model's function)。自訂model相關函式,用this拿到該model的屬性(例如.find)。
```js
animalSchema.statics.findByName = function(name) {
return this.find({ name: new RegExp(name, 'i') });
};
```
5. [virtual type](https://mongoosejs.com/docs/tutorials/virtuals.html):可以根據doc自訂回傳格式(例如doc → res.json),不會存入資料庫。有getter和setter屬性。
* [model usage](https://mongoosejs.com/docs/api/model.html)
* insert
```js
const doc = await new Login({
account:"b07901029"
}).save().catch(dbCatch)//throw error if error occur
```
* find
[query and selector rule](https://docs.mongodb.com/manual/reference/operator/query/)
```js
//只找一個
const doc = await Login
.findOne(query, selector)
.catch(err=>{})
//找多個
const docs = await Login
.find(query,selector)
.catch(err=>{})
```
* update
[update rule](https://docs.mongodb.com/manual/reference/operator/update/)
```js
const {updateQuery} = require(../Schemas/query)
//updateQuery會把特定的object轉成mongoose看得懂的格式
//簡單說就是依據value是不是空字串,把value塞進{$set,$unset}
const {title} = req.body
const update = updateQuery({'title.title':title})
//update = {$set:{'title.title':title}}
const {n,nModified} = await Recruitment
.updateOne(query,update,option)
.catch(err=>{})
/*{
n, //number of matched doc
nModified //number of modified doc
}*/
```
* delete
```js
loginModel.deleteMany(query,function(err){});
```
---
## 其他
#### res.send
* [http code](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81)
* 可以用throw ErrorHandler(404,'any msg u want')觸發error router
#### api doc
* 在/backend跑yarn doc根據每個檔案的註解生成README
---
## References
* **Learn Coding Websites**
[codecadamy(HTML, CSS, JS)](https://www.codecademy.com/learn)
[freeCodeCamp youtube channel](https://www.youtube.com/channel/UC8butISFwT-Wl7EV0hUK0BQ)
* 基礎能力學習
[Git 簡介](https://zlargon.gitbooks.io/git-tutorial/content/installation.html)
[Git environment setup](https://www.tutorialspoint.com/git/git_environment.htm?fbclid=IwAR07byYUJfy-Ds6Y-Qc6f_mwyF4V5KLlDq0v_zc5_f2grH1Lp7qMA5HbRk8)
[Command Line 簡介](https://carolhsu.gitbooks.io/django-girls-tutorial-traditional-chiness/content/intro_to_command_line/README.html)
* **Ric's Course**
[link](https://ric2k1.github.io/?fbclid=IwAR0pd7K5m3Dlh_riCEIRq88tXy7InQshxinUhM-bIfmEhRTkwPtpF3ljwOI)
* **資料庫管理SQL**
[w3school](https://www.w3schools.com/sql/)
[一天速成](http://www.finereport.com/tw/knowledge/acquire/sql-3.html?fbclid=IwAR3if2FnWsZL_T03NDqQwmZVzIy2hmbxucM3r8tXZ-DVAGVfaCkZNSwOErY)
* **Node.js & Express.js & Mongo.js**
[w3school](https://www.w3schools.com/nodejs/default.asp)
[express_Ric](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website)
[Node.js full module list](https://www.w3schools.com/nodejs/ref_modules.asp)
[Express.js MDN tutorial](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website)
[Express.js & Node.js video](https://www.youtube.com/watch?v=G8uL0lFFoN0)
[Ric's ppt](https://hackmd.io/@dSpEyyWWQYaN4gOKsy7saw/HJegFNM54?type=slide#/8)
* **JavaScript**
[w3school](https://www.w3schools.com/js)
[3hr video](https://www.youtube.com/watch?v=PkZNo7MFNFg)
* **HTML**
[w3school](https://www.w3schools.com/html/)
[2hr video](https://www.youtube.com/watch?v=pQN-pnXPaVg)
* **CSS**
[w3school](https://www.w3schools.com/css/)
[2hr video](https://www.youtube.com/watch?v=ieTHC78giGQ)