node-gotoo
---
這個模組是為了[跳跳前端製作所](https://gotoo.co)後端部門開發專案而開發,提供可覆用元件,函數及物件,本專案不含任何專利項目因此以 MIT LICENSE 開源。
+ Developer: FloatFlower.Huang
+ Email: floatflower@gotoo.co
## Test Coverage
```
------------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------------------------|----------|----------|----------|----------|-------------------|
All files | 99.86 | 94.38 | 100 | 100 | |
lib/data-handler | 100 | 100 | 100 | 100 | |
handle-boolean.js | 100 | 100 | 100 | 100 | |
handle-float.js | 100 | 100 | 100 | 100 | |
handle-integer.js | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
is-disposable-email.js | 100 | 100 | 100 | 100 | |
is-empty-object.js | 100 | 100 | 100 | 100 | |
is-valid-email.js | 100 | 100 | 100 | 100 | |
is-valid-json.js | 100 | 100 | 100 | 100 | |
lib/filter | 100 | 100 | 100 | 100 | |
base.js | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
lib/repository | 98.51 | 90.38 | 100 | 100 | |
base.js | 100 | 93.48 | 100 | 100 | 34,67,103 |
index.js | 100 | 100 | 100 | 100 | |
user.js | 91.67 | 66.67 | 100 | 100 | 9,27 |
------------------------------|----------|----------|----------|----------|-------------------|
```
## Installation
```bash
npm install https://github.com/gotoo-co/gotoo.git --save
```
## Usage
+ [DataHandler](#DataHandler)
+ [handleBoolean](#handleBoolean)
+ [handleFloat](#handleFloat)
+ [handleInteger](#handleInteger)
+ [isDisposableEmail](#handleInteger)
+ [isEmptyObject](#isEmptyObject)
+ [isValidEmail](#isValidEmail)
+ [isValidJSON](#isValidJSON)
+ [Repository](#Repository)
+ [BaseRepository](#BaseRepository)
+ [UserRepository](#UserRepository)
+ [Websocket](#Websocket)
+ [APIKeyGenerator](#APIKeyGenerator)
+ [BearerTokenHandler](#BearerTokenHandler)
+ [Filter](#Filter)
+ [HTTP Status](#HTTP-Status)
+ [Initiate Application](#Initiate-Application)
+ [CORS](#CORS)
+ [Trace](#Trace)
+ [Bus](#Bus)
### import module
```javascript
const gotoo = require('gotoo-backend');
```
### DataHandler
```javascript
const dataHandler = require('gotoo-backend').dataHandler;
```
#### handleBoolean
+ 當傳入**字串**型態的`true`則會得到**布林**型態`true`,其餘的任何字串皆會被視為`false`。
+ 當傳入**整數**型態時,除了0會回傳`false`之外其餘皆返回`true`。
```javascript
// true
let handled = dataHandler.handleBoolean("true");
// true
let handled = dataHandler.handleBoolean(100);
// false
let handled = dataHandler.handleBoolean("false");
// false
let handled = dataHandler.handleBoolean("whatever");
// false
let handled = dataHandler.handleBoolean(0);
```
#### handleFloat
+ 當傳入可被解析的**浮點數字串**,則會返回該值,否則在尚未設定預設值的狀態下,會返回0,若設定預設值則會返回預設值。
```javascript
// 13.23
let handled = dataHandler.handleFloat("13.23");
// 12.23
let handled = dataHandler.handleFloat("12.23", 1.2);
// 0
let handled = dataHandler.handleFloat("ttt");
// 2.3
let handled = dataHandler.handleFloat("foo", 2.3);
```
#### handleInteger
+ 當傳入可被解析的**整數字串**,則會返回該值,否則在尚未設定預設值的狀態下,會返回0,若設定預設值則會返回預設值。
+ 若傳入base,則第一個參數將會被視為該進位法。
```javascript
// 3
let handled = dataHandler.handleInteger("3");
// 0
let handled = dataHandler.handleInteger("ttt");
// 12
let handled = dataHandler.handleInteger("ttt", 12);
// 24
let handled = dataHandler.handleInteger("18", 12, 16);
// 12
let handled = dataHandler.handleInteger("ttt", 12, 16);
```
#### isDisposableEmail
這個函數定義了[4000多個用完即丟的電子郵件網域](https://github.com/gotoo-co/gotoo-backend/wiki/Disposable-Email-List),用於檢測電子郵件是否註冊於該服務。
```javascript
// true
let handled = dataHandler.isDisposableEmail('floatflower@0-mail.com');
// true
let handled = dataHandler.isDisposableEmail('floatflower@sharklasers.com');
// true
let handled = dataHandler.isDisposableEmail('floatflower@shitaway.ml');
// false
let handled = dataHandler.isDisposableEmail('floatflower@gmail.com');
```
#### isEmptyObject
+ 傳入 null 將被視為空物件。
+ 當物件中具有`length`這個變數,若`length === 0`也會被視為空物件。
+ 當傳入的型態不為物件時,也會被視為空物件。
+ 當物件中在`prototype`被定義屬性,而不是在本身定義屬性,並不會被算入到本身屬性中。
```javascript
// true
let handled = dataHandler.isEmptyObject({});
// false
let obj = {a: 1};
let handled = dataHandler.isEmptyObject(obj);
// true
let obj = {length: 0, a: 1};
let handled = dataHandler.isEmptyObject(obj);
// true
let obj = 1;
let handled = dataHandler.isEmptyObject(obj);
// true
Object.prototype.foo = 1;
let obj = {};
let handled = dataHandled.isEmptyObject(obj);
```
#### isValidEmail
這個函數透過以下這個正規表達式檢測是否傳入的字串為正確格式的電子郵件。
```regexp
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([^\-][a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
```
**符合規定的電子郵件**
| 範例 | 原因 |
| -------- | -------- |
| floatflower@gmail.com | 一般電子郵件 |
| float.flower@gmail.com | 電子郵件地址區塊中含有點 |
| floatflower@mail.gotoo.co | 電子郵件包含子網域 |
| float+flower@gmail.com | 電子郵件中包含+號 |
| floatflower@123.123.123.123 | 電子郵件中包含正確格式的IP |
| floatflower@\[123.123.123.123] | 電子郵件中包含利用中括號括起來的IP |
| "floatflower"@gmail.com | 利用雙引號包含帳號 |
| floatflower1029@gmail.com | 帳號包含數字 |
| \_\_\_\_floatflower@gmail.com | 帳號包含底線 |
| -floatflower@gmail.com | 帳號包含-號 |
**不符合規定的電子郵件**
| 範例 | 原因 |
| -------- | -------- |
| floatflower | 缺少@及網域 |
| #@%^%#\$@#\$@#.com | 垃圾 |
| @gmail.com | 缺少帳號 |
| FloatFlower \<developer@gotoo.co> | 包含編碼過的HTML |
| floatflower.gmail.com | 缺少@ |
| floatflower@gmail@com | 雙重@ |
| .floatflower@gmail.com | 帳號具有前綴. |
| floatflower.@gmail.com | 帳號具有後綴. |
| float..flower@gmail.com | 多重. |
| developer@gotoo.co (FloatFlower) | 電子郵件後帶有文字 |
| floatflower@gmail | 缺少頂級網域 |
| floatflower@-gmail.com | 網域前具有- |
| floatflower@111.122.333.444444 | 不合法的IP格式 |
| floatflower@gmail..com | 網域中具有多重的點 |
```javascript
// true
let hanled = dataHandler.isValidEmail(floatflower@gmail.com);
// false
let handled = dataHandler.isValidEmail(float..flower@gmail.com);
```
#### isValidJSON
這個函數將嘗試解析JSON,若解析失敗則會`return false`,成功則`return true`。
### Repository
```javascript
const Repository = require('gotoo-backend').repository;
```
#### BaseRepository
```javascript
const knexConfig = require(PATH_TO_KNEX_FILE);
const knex = require('knex')(knexConfig);
class User extends Repository.Base
{
constructor() {
super(knex, 'user');
}
}
```
**`find(id, config = {})`**
透過傳入id來尋找實體,也可以透過 config 物件來重設用於查詢的column name。
```javascript
let user = new User();
return user.find(1, {
// if username is primary key
// key: 'username'
})
.then((user) => {
if(!user) {
// user not found
}
// user founded
})
```
**`findBy(criteria = {}, orederBy = {}, config = {})`**
透過傳入的ceiteria尋找用戶,並可透過傳入orderBy的物件將物件進行排列。此外也可以透過config對這個查詢增加`limit`及`offset`。
```javascript
let user = new User();
return user.findBy({
group_id: 1
}, {
age: 'desc'
}, {
// data limit
// limit: 2,
// data offset
// offset: 1
})
.then((users) => {
// collections of users
})
```
**`findOneBy(criteria = {}, orederBy = {}, config = {})`**
```javascript
let user = new User();
return user.findBy({
username: 'floatflower'
}, {
age: 'desc'
})
.then((user) => {
if(!user) {
// user not found
}
// user founded
})
```
**`count(criteria = {}, config = {})`**
```javascript
let user = new User();
return user.count({
group_id: 1
})
.then((amount) => {
// user amount
})
```
**`remove(id, config = {})`**
透過提供的主鍵值刪除實體,與`find`相同的是,也可以透過`config`重設用於尋找的key的column name。
```javascript
let user = new User();
return user.remove(1, {
// key: 'username'
});
```
#### UserRepository
UserRepository 繼承自 BaseRepository,因此這個 class 也支援`find()`,`findBy()`等由BaseRepository提供的函數。
```javascript
const knexConfig = require(PATH_TO_KNEX_FILE);
const knex = require('knex')(knexConfig);
class User extends Repository.User
{
constructor() {
super(knex, 'user');
}
}
```
**`findByLoginKey(value, key = ['username'], config = {})`**
透過傳入value來尋找用戶,可以透過key來決定要用於尋找的column name,所有的key會以`orWhere`來尋找。
```javascript
let user = new User();
return user.findByLoginKey('floatflower', key=['username', 'phone', 'email']).then((user) => {
if(!user) {
// user not found
}
// user founded
})
```
### Websocket
```javascript
const websocket = require('gotoo-backend').websocket;
```
**register(key, socket, events)**
將 socket 註冊到 sockets pool 中並聲明這個 socket 可以收到的事件類型。
```javascript
let s = getSocketFromServer();
websocket.register('floatflower', s, ['product.add', 'product.remove']);
```
**unregister(key)**
從 socket pool 中移出指定的 socket,並取消所有事件的註冊。
```javascript
websocket.unregister('floatflower');
```
**dispatch(eventName, payload)**
向所有有註冊該事件類型的 socket 推送該內容。
```javascript
let s = getSocketFromServer();
websocket.dispatch('product.add', {
product_name: '牛排',
price: 100
});
```
**subscribe(key, events)**
新增訂閱新的事件類型。
```javascript
websocket.subscribe('floatflower', ['product.edit']);
```
**unsubscribe(key, events)**
取消訂閱新的事件類型。
```javascript
websocket.subscribe('floatflower', ['product.add']);
```
**push(keys, payload)**
向指定的 sockets 推送無名的消息內容。
```javascript
websocket.push(['floatflower', 'johncena'], {
message: 'hello'
})
```
### APIKeyGenerator
```javascript
const APIKeyGenerator = require('gotoo-backend').apiKeyGenerator;
// {
// key: 'MNB1TSYURJ8T1DCMRSRMHUMMK9LDS90K',
// secret: 'AOJA7Y3S8TXWSZVDP0D2OR98XC56X4N8',
// hash: '3daa4c77abb076fddeeaeeeda9f27bba2cd56860bc617502ef243f1e84b3c8a4f7a06bc030b3bd16f66342f0a5b5a7c0b61e7c72efb03321e51c559e468cf64d'
// }
let generated = APIKeyGenerator(32, 32, 'MN');
```
### BearerTokenHandler
```javascript
const bearerTokenHandler = require('gotoo-backend').bearerTokenHandler;
// mount handler at route.
router.get('/', bearerTokenHandler);
```
### Filter
```javascript
const Filter = require('gotoo-backend').filter;
class UserFilter extends Filter.Base
{
constructor() {
super();
this.registerHandler("foo", this.handlerFoo);
}
defaultHandler(data) {
return {
id: data.id,
username: data.username
}
}
handlerFoo(data) {
return {
id: data.id,
username: data.username,
email: data.email,
phone: data.phone,
age: data.age,
country: data.country,
birthday: data.birthday
}
}
}
// usage
let user = {
id: 1,
username: 'floatflower',
email: 'floatflower@gotoo.co',
phone: '0911693232',
age: 22,
country: 'Republic of China',
birthday: '1996-10-20',
password: 'ehrewulkhrfsekfheiwusjsmdfasdjfhasdjkfhasdlfkasjdfh',
create_at: '2019-07-21 23:12:32',
};
let filter = new UserFilter();
// existed handler
// {
// id: 1,
// username: 'floatflower',
// email: 'floatflower@gotoo.co',
// phone: '0911693232',
// age: 22,
// country: 'Republic of China',
// birthday: '1996-10-20',
// }
let result = filter.handle("foo", user);
// non-existed handler, fallback to defaultHandler
// // {
// id: 1,
// username: 'floatflower'
// }
let result = filter.handle("bar", user);
let users = [
{
id: 1,
username: 'floatflower',
email: 'floatflower@gotoo.co',
phone: '0911693232',
age: 22,
country: 'Republic of China',
birthday: '1996-10-20',
password: 'ehrewulkhrfsekfheiwusjsmdfasdjfhasdjkfhasdlfkasjdfh',
create_at: '2019-07-21 23:12:32',
},
{
id: 2,
username: 'floatflower2',
email: 'floatflower2@gotoo.co',
phone: '0922693232',
age: 22,
country: 'Republic of China',
birthday: '1996-12-20',
password: 'ehrewulkhrfsekfheiwusjsmdfasdjfhasdjkfhasdlfkasjdfh',
create_at: '2019-08-21 23:12:32',
}
];
// non-existed handler, fallback to defaultHandler
// [
// {
// id: 1,
// username: 'floatflower'
// },
// {
// id: 2,
// username: 'floatflower2'
// }
// ]
let result = filter.handle("bar", users);
```
### HTTP Status
```javascript
module.exports = {
CONTINUE: 100,
SWITCHING_PROTOCOL: 101,
PROCESSING: 102,
EARLY_HINTS: 103,
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NON_AUTHORITATIVE_INFORMATION: 203,
NO_CONTENT: 204,
RESET_CONTENT: 205,
PARTIAL_CONTENT: 206,
MULTI_STATUS: 207,
IM_USED: 226,
MULTIPLE_CHOICE: 300,
MOVED_PERMANENTLY: 301,
FOUND: 302,
SEE_OTHER: 303,
NOT_MODIFIED: 304,
USE_PROXY: 305,
UNUSED: 306,
TEMPORARY_REDIRECT: 307,
PERMANENT_REDIRECT: 308,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
PAYMENT_REQUIRED: 402,
FORBIDDEN: 403,
NOT_FOUND: 404,
METHOD_NOT_ALLOW: 405,
NOT_ACCEPTABLE: 406,
PROXY_AUTHENTICATION_REQUIRED: 407,
REQUEST_TIMEOUT: 408,
CONFLICT: 409,
GONE: 410,
LENGTH_REQUIRE: 411,
PRECONDITION_FAILED: 412,
PAYLOAD_TOO_LARGE: 413,
URI_TOO_LONG: 414,
UNSUPPORTED_MEDIA_TYPE: 415,
REQUESTED_RANGE_NOT_SATISFIABLE: 416,
EXPECTATION_FAILED: 417,
IM_A_TEAPOT: 418,
MISDIRECTED_REQUEST: 421,
UNPROCESSABLE_ENTITY: 422,
LOCKED: 423,
FAILED_DEPENDENCY: 424,
UPGRADE_REQUIRED: 426,
PRECONDITION_REQUIRED: 428,
TOO_MANY_REQUESTS: 429,
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504,
HTTP_VERSION_NOT_SUPPORTED: 505,
VARIANT_ALSO_NEGOTIATES: 506,
INSUFFICIENT_STORAGE: 507,
LOOP_DETECTED: 508,
NOT_EXTENDED: 510,
NETWORK_AUTHENTICATION_REQUIRED: 511
};
```
### Initiate Application
這個函數會為 req 物件提供 req.data,用於應用程式在 middleware 之間傳遞資料。
```javascript
const initApp = require('gotoo-backend').initApp;
// use in express app.
app.use(initApp);
```
### CORS
```javascript
const CORS = require('gotoo-backend').cors;
// use in express app.
// CORS(
// origin = "*",
// methods = "GET,PUT,POST,DELETE,PATCH,OPTIONS",
// headers = "Origin,X-Requested-With,Content-Type,Accept,Authorization",
// allowCredentials = true
// );
app.use(CORS());
```
### Trace
調用該函數之後回傳值為調用該函數的檔案名稱及行數。
```javascript
const trace = require('gotoo-backend').trace;
trace();
```
### Bus
**Emit event**
在應用程式中可以在任何地方`emit`事件。
```javascript
const bus = require('gotoo-backend').bus;
bus.emit('some_event', {
foo: 'bar'
})
```
**Evnet handler**
在應用程式的另一處可以透過 middleware 註冊event handler
```javascript
const bus = require('gotoo-backend').bus;
bus.on('some_event', (payload) => {
// do something with payload
})
```