first submit
commit
6d64344c31
@ -0,0 +1,6 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
coverage
|
||||||
|
npm-debug.log
|
||||||
|
tmp
|
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
// JSHint Default Configuration File (as on JSHint website)
|
||||||
|
// See http://jshint.com/docs/ for more details
|
||||||
|
|
||||||
|
"maxerr" : 50, // {int} Maximum error before stopping
|
||||||
|
|
||||||
|
// Enforcing
|
||||||
|
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||||
|
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||||
|
"curly" : false, // true: Require {} for every new block or scope
|
||||||
|
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||||
|
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||||
|
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
|
||||||
|
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||||
|
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||||
|
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||||
|
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||||
|
"noempty" : true, // true: Prohibit use of empty blocks
|
||||||
|
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
|
||||||
|
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||||
|
"plusplus" : false, // true: Prohibit use of `++` and `--`
|
||||||
|
"quotmark" : false, // Quotation mark consistency:
|
||||||
|
// false : do nothing (default)
|
||||||
|
// true : ensure whatever is used is consistent
|
||||||
|
// "single" : require single quotes
|
||||||
|
// "double" : require double quotes
|
||||||
|
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||||
|
"unused" : true, // Unused variables:
|
||||||
|
// true : all variables, last function parameter
|
||||||
|
// "vars" : all variables only
|
||||||
|
// "strict" : all variables, all function parameters
|
||||||
|
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
|
||||||
|
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||||
|
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||||
|
"maxstatements" : false, // {int} Max number statements per function
|
||||||
|
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||||
|
"maxlen" : false, // {int} Max number of characters per line
|
||||||
|
"varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed.
|
||||||
|
|
||||||
|
// Relaxing
|
||||||
|
"asi" : true, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||||
|
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||||
|
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||||
|
"eqnull" : false, // true: Tolerate use of `== null`
|
||||||
|
"esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere.
|
||||||
|
"moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||||
|
"esnext" : true,
|
||||||
|
// (ex: `for each`, multiple try/catch, function expression…)
|
||||||
|
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||||
|
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
|
||||||
|
"funcscope" : false, // true: Tolerate defining variables inside control statements
|
||||||
|
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||||
|
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||||
|
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||||
|
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||||
|
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||||
|
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||||
|
"multistr" : false, // true: Tolerate multi-line strings
|
||||||
|
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
|
||||||
|
"notypeof" : false, // true: Tolerate invalid typeof operator values
|
||||||
|
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||||
|
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||||
|
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||||
|
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||||
|
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||||
|
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||||
|
|
||||||
|
// Environments
|
||||||
|
"devel" : true, // Development/debugging (alert, confirm, etc)
|
||||||
|
"jquery" : true, // jQuery
|
||||||
|
"node" : true, // Node.js
|
||||||
|
"browser" : true, // Browser
|
||||||
|
|
||||||
|
|
||||||
|
// Custom Globals
|
||||||
|
"globals" : {} // additional predefined global variables
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
language: node_js
|
||||||
|
|
||||||
|
services:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- $HOME/.npm
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
||||||
|
node_js:
|
||||||
|
- '8'
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm i -g npm@^5.5.1
|
||||||
|
- npm i -g lerna@^2.5.1
|
||||||
|
- mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci'
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm install
|
||||||
|
- npm run create-db
|
||||||
|
- npm run check
|
||||||
|
|
||||||
|
after_success:
|
@ -0,0 +1,28 @@
|
|||||||
|
# rap2-delos CE
|
||||||
|
|
||||||
|
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
|
||||||
|
|
||||||
|
RAP2 服务端。
|
||||||
|
|
||||||
|
http://rap2api.taobao.org
|
||||||
|
|
||||||
|
## 本地开发
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
或者
|
||||||
|
|
||||||
|
```
|
||||||
|
# 在任意文件变化后自动运行测试用例
|
||||||
|
npm run watch-test
|
||||||
|
# 在任意文件变化后自动运行指定的测试用例
|
||||||
|
npm run watch-test test/test.repository.js
|
||||||
|
```
|
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
version: '2.3',
|
||||||
|
serve: {
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
keys: ['some secret hurr'],
|
||||||
|
session: {
|
||||||
|
key: 'rap2:sess'
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
dialect: 'mysql',
|
||||||
|
host: 'localhost',
|
||||||
|
port: '3306',
|
||||||
|
username: 'root',
|
||||||
|
password: '', // KeyCenter 配置项密文
|
||||||
|
database: 'RAP2_DELOS_APP',
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
idle: 10000
|
||||||
|
},
|
||||||
|
logging: false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
version: '2.3',
|
||||||
|
serve: {
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
keys: ['some secret hurr'],
|
||||||
|
session: {
|
||||||
|
key: 'rap2:sess'
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
dialect: 'mysql',
|
||||||
|
host: 'localhost',
|
||||||
|
port: '3306',
|
||||||
|
username: 'root',
|
||||||
|
password: '',
|
||||||
|
database: 'RAP2_DELOS_APP_LOCAL',
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
idle: 10000
|
||||||
|
},
|
||||||
|
logging: true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
version: '2.3',
|
||||||
|
serve: {
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
keys: ['some secret hurr'],
|
||||||
|
session: {
|
||||||
|
key: 'rap2:sess'
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
dialect: 'mysql',
|
||||||
|
host: 'localhost',
|
||||||
|
port: '3306',
|
||||||
|
username: 'root',
|
||||||
|
password: '',
|
||||||
|
database: 'RAP2_DELOS_APP',
|
||||||
|
pool: {
|
||||||
|
max: 5,
|
||||||
|
min: 0,
|
||||||
|
idle: 10000
|
||||||
|
},
|
||||||
|
logging: true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
// local or development or production
|
||||||
|
module.exports =
|
||||||
|
(process.env.NODE_ENV === 'local' && require('./config.local')) ||
|
||||||
|
(process.env.NODE_ENV === 'development' && require('./config.dev')) ||
|
||||||
|
require('./config.prod')
|
@ -0,0 +1,30 @@
|
|||||||
|
// process.env.NODE_ENV = 'production'
|
||||||
|
|
||||||
|
// http://gitlab.alibaba-inc.com/thx/rap2-dolores/commit/2fd70fdcaa9d179e9cf95e530e37f31f8488f432
|
||||||
|
// https://nodejs.org/api/cluster.html
|
||||||
|
// https://github.com/node-modules/graceful/blob/master/example/express_with_cluster/dispatch.js
|
||||||
|
// http://gitlab.alibaba-inc.com/mm/fb/blob/master/dispatch.js
|
||||||
|
|
||||||
|
let cluster = require('cluster')
|
||||||
|
let path = require('path')
|
||||||
|
let now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
|
||||||
|
|
||||||
|
cluster.setupMaster({
|
||||||
|
exec: path.join(__dirname, 'scripts/worker.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
require('os').cpus().forEach((cpu, index) => {
|
||||||
|
cluster.fork()
|
||||||
|
})
|
||||||
|
cluster.on('listening', (worker, address) => {
|
||||||
|
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} is now connected to ${address.address}:${address.port}.`)
|
||||||
|
})
|
||||||
|
cluster.on('disconnect', (worker) => {
|
||||||
|
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} has disconnected.`)
|
||||||
|
})
|
||||||
|
cluster.on('exit', (worker, code, signal) => {
|
||||||
|
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} died (${signal || code}). restarting...`)
|
||||||
|
cluster.fork()
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "rap2-delos",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"repository": {
|
||||||
|
"url": ""
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"main": "dispatch.js",
|
||||||
|
"scripts": {
|
||||||
|
"create-db": "node scripts/init",
|
||||||
|
"dev": "NODE_ENV=development nodemon --watch scripts --watch src scripts/dev.js",
|
||||||
|
"dev-local": "NODE_ENV=local nodemon --watch scripts --watch src scripts/dev.js",
|
||||||
|
"start": "NODE_ENV=production node dispatch.js",
|
||||||
|
"check": "npm run linter && mocha",
|
||||||
|
"test": "NODE_ENV=development mocha",
|
||||||
|
"linter": "standard --fix",
|
||||||
|
"watch-test": "NODE_ENV=development nodemon --watch scripts --watch src --watch test ./node_modules/.bin/mocha --timeout 5000",
|
||||||
|
"watch-test-local": "NODE_ENV=local nodemon --watch scripts --watch src --watch test ./node_modules/.bin/mocha --timeout 5000"
|
||||||
|
},
|
||||||
|
"author": "mozhi.gyy@alibaba-inc.com, bosn@outlook.com",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^1.1.3",
|
||||||
|
"graceful": "^1.0.1",
|
||||||
|
"js-beautify": "^1.6.9",
|
||||||
|
"kcors": "^2.2.1",
|
||||||
|
"koa": "^2.2.0",
|
||||||
|
"koa-body": "^2.0.0",
|
||||||
|
"koa-logger": "^2.0.1",
|
||||||
|
"koa-router": "^7.1.1",
|
||||||
|
"koa-send": "^4.0.0",
|
||||||
|
"koa-session": "^5.0.0",
|
||||||
|
"koa-static": "^3.0.0",
|
||||||
|
"mockjs": "^1.0.1-beta3",
|
||||||
|
"moment": "^2.17.1",
|
||||||
|
"mysql": "^2.11.1",
|
||||||
|
"node-fetch": "^1.7.1",
|
||||||
|
"node-print": "0.0.4",
|
||||||
|
"sequelize": "^3.30.4",
|
||||||
|
"sequelize-cli": "^3.1.0",
|
||||||
|
"underscore": "^1.8.3",
|
||||||
|
"urllib": "^2.22.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-eslint": "^7.2.3",
|
||||||
|
"chai": "^3.5.0",
|
||||||
|
"mocha": "^3.3.0",
|
||||||
|
"nodemon": "^1.11.0",
|
||||||
|
"npm-run-all": "^4.0.2",
|
||||||
|
"pre-commit": "^1.2.2",
|
||||||
|
"standard": "^10.0.2",
|
||||||
|
"supertest": "^3.0.0"
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"globals": [],
|
||||||
|
"ignore": []
|
||||||
|
},
|
||||||
|
"pre-commit": [
|
||||||
|
"linter"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"install-node": "9.2.0"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<title>RAP2 Delos</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="https://rap2.alibaba-inc.com">https://rap2.alibaba-inc.com</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,6 @@
|
|||||||
|
## RAP2 提供两种拦截方式
|
||||||
|
|
||||||
|
1. 引入 `libs/jquery.rap.js`
|
||||||
|
用复写的 `jQuery.ajax` 拦截与 RAP2 接口设置匹配的 ajax 请求,然后转发至 RAP2。
|
||||||
|
2. 引入 `libs/mock.rap.js`
|
||||||
|
由 Mock 拦截与 RAP2 接口设置匹配的 ajax 请求,然后直接返回响应数据,不会转发至 RAP2。
|
@ -0,0 +1,40 @@
|
|||||||
|
;(function (RAP, fetch) {
|
||||||
|
if (!fetch) {
|
||||||
|
console.warn('当前环境不支持 fetch')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!RAP) {
|
||||||
|
console.warn('请先引入 RAP 插件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = fetch
|
||||||
|
let find = (settings) => {
|
||||||
|
for (let repositoryId in RAP.interfaces) {
|
||||||
|
for (let itf of RAP.interfaces[repositoryId]) {
|
||||||
|
if (itf.method.toUpperCase() === settings.method.toUpperCase() && itf.url === settings.url) {
|
||||||
|
return Object.assign({}, itf, { repositoryId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.fetch = function (url, settings) {
|
||||||
|
// ajax(settings)
|
||||||
|
if (typeof url === 'object') {
|
||||||
|
settings = Object.assign({ method: 'GET' }, url)
|
||||||
|
} else {
|
||||||
|
// ajax(url) ajax(url, settings)
|
||||||
|
settings = Object.assign({ method: 'GET' }, settings, { url })
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = find(settings)
|
||||||
|
if (!match) return next.call(window, url, settings)
|
||||||
|
|
||||||
|
let redirect = `${RAP.protocol}://${RAP.host}/app/mock/${match.repositoryId}/${match.method}/${match.url}`
|
||||||
|
settings.credentials = 'include'
|
||||||
|
settings.method = 'GET'
|
||||||
|
settings.dataType = 'jsonp'
|
||||||
|
console.log(`Fetch ${match.method} ${match.url} => ${redirect}`)
|
||||||
|
return next.call(window, redirect, settings)
|
||||||
|
}
|
||||||
|
})(window.RAP, window.fetch)
|
@ -0,0 +1,57 @@
|
|||||||
|
;(function (RAP, jQuery) {
|
||||||
|
if (!jQuery) {
|
||||||
|
console.warn('请先引入 jQuery')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!RAP) {
|
||||||
|
console.warn('请先引入 RAP 插件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例:检测重复接口
|
||||||
|
let counter = {}
|
||||||
|
for (let repositoryId in RAP.interfaces) {
|
||||||
|
for (let itf of RAP.interfaces[repositoryId]) {
|
||||||
|
let key = `${itf.method} ${itf.url}`
|
||||||
|
counter[key] = [...(counter[key] || []), itf]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let key in counter) {
|
||||||
|
if (counter[key].length > 1) {
|
||||||
|
console.group('警告:检测到重复接口 ' + key)
|
||||||
|
counter[key].forEach(itf => {
|
||||||
|
console.warn(`#${itf.id} ${itf.method} ${itf.url}`)
|
||||||
|
})
|
||||||
|
console.groupEnd('警告:检测到重复接口 ' + key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = jQuery.ajax
|
||||||
|
let find = (settings) => {
|
||||||
|
for (let repositoryId in RAP.interfaces) {
|
||||||
|
for (let itf of RAP.interfaces[repositoryId]) {
|
||||||
|
if (itf.method.toUpperCase() === settings.method.toUpperCase() && itf.url === settings.url) {
|
||||||
|
return Object.assign({}, itf, { repositoryId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jQuery.ajax = function (url, settings) {
|
||||||
|
// ajax(settings)
|
||||||
|
if (typeof url === 'object') {
|
||||||
|
settings = Object.assign({ method: 'GET' }, url)
|
||||||
|
} else {
|
||||||
|
// ajax(url) ajax(url, settings)
|
||||||
|
settings = Object.assign({ method: 'GET' }, settings, { url })
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = find(settings)
|
||||||
|
if (!match) return next.call(jQuery, url, settings)
|
||||||
|
|
||||||
|
let redirect = `${RAP.protocol}://${RAP.host}/app/mock/${match.repositoryId}/${match.method}/${match.url}`
|
||||||
|
settings.method = 'GET'
|
||||||
|
settings.dataType = 'jsonp'
|
||||||
|
console.log(`jQuery ${match.method} ${match.url} => ${redirect}`)
|
||||||
|
return next.call(jQuery, redirect, settings)
|
||||||
|
}
|
||||||
|
})(window.RAP, window.jQuery)
|
@ -0,0 +1,19 @@
|
|||||||
|
;(function (RAP, Mock) {
|
||||||
|
if (!RAP) {
|
||||||
|
console.warn('请先引入 RAP 插件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!Mock) {
|
||||||
|
console.warn('请先引入 Mock')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let repositoryId in RAP.interfaces) {
|
||||||
|
for (let itf of RAP.interfaces[repositoryId]) {
|
||||||
|
Mock.mock(itf.url, itf.method.toLowerCase(), (settings) => {
|
||||||
|
console.log(`Mock ${itf.method} ${itf.url} =>`, itf.response)
|
||||||
|
return Mock.mock(itf.response)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(window.RAP, window.Mock)
|
@ -0,0 +1,5 @@
|
|||||||
|
<ul>
|
||||||
|
<li><a href='./test.plugin.jquery.html'>jQuery</a></li>
|
||||||
|
<li><a href='./test.plugin.mock.html'>Mock</a></li>
|
||||||
|
<li><a href='./test.plugin.fetch.html'>Fetch</a></li>
|
||||||
|
</ul>
|
@ -0,0 +1,14 @@
|
|||||||
|
<script src="//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js"></script>
|
||||||
|
<script src="//g.alicdn.com/thx/brix-deps/nuysoft@Mock/1.0.1-beta3/dist/mock.js"></script>
|
||||||
|
<div id="result"></div>
|
||||||
|
<script src="./test.request.js"></script>
|
||||||
|
<script>
|
||||||
|
const id = /id=(\d+)/.exec(location.search) || ['', 1]
|
||||||
|
if (id) {
|
||||||
|
$.getScript(`/app/plugin/${id[1]}`).then(() => {
|
||||||
|
$.getScript(`/libs/fetch.rap.js`).then(() => {
|
||||||
|
doRequest(window.RAP, window.fetch)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,14 @@
|
|||||||
|
<script src="//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js"></script>
|
||||||
|
<script src="//g.alicdn.com/thx/brix-deps/nuysoft@Mock/1.0.1-beta3/dist/mock.js"></script>
|
||||||
|
<div id="result"></div>
|
||||||
|
<script src="./test.request.js"></script>
|
||||||
|
<script>
|
||||||
|
const id = /id=(\d+)/.exec(location.search) || ['', 1]
|
||||||
|
if (id) {
|
||||||
|
$.getScript(`/app/plugin/${id[1]}`).then(() => {
|
||||||
|
$.getScript(`/libs/jquery.rap.js`).then(() => {
|
||||||
|
doRequest(window.RAP)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,15 @@
|
|||||||
|
<script src="//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js"></script>
|
||||||
|
<script src="//g.alicdn.com/thx/brix-deps/nuysoft@Mock/1.0.1-beta3/dist/mock.js"></script>
|
||||||
|
<div id="result"></div>
|
||||||
|
<script src="./test.request.js"></script>
|
||||||
|
<script>
|
||||||
|
const id = /id=(\d+)/.exec(location.search) || ['', 1]
|
||||||
|
if (id) {
|
||||||
|
$.getScript(`/app/plugin/${id[1]}`).then(() => {
|
||||||
|
$.getScript(`/libs/mock.rap.js`).then(() => {
|
||||||
|
doRequest(window.RAP)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,25 @@
|
|||||||
|
/* global $ */
|
||||||
|
const appendData = (repositoryId, itf, data) => {
|
||||||
|
$('#result').append(`<div>
|
||||||
|
<strong>#${repositoryId} #${itf.id} ${itf.name} ${itf.method} ${itf.url}</strong>
|
||||||
|
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||||
|
</div>`)
|
||||||
|
}
|
||||||
|
const doRequest = (RAP, fetch) => { // eslint-disable-line no-unused-vars
|
||||||
|
for (let repositoryId in RAP.interfaces) {
|
||||||
|
RAP.interfaces[repositoryId].forEach(itf => {
|
||||||
|
if (fetch) {
|
||||||
|
fetch(itf.url, { method: itf.method })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
appendData(repositoryId, itf, data)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$.ajax({ url: itf.url, method: itf.method, dataType: 'json' })
|
||||||
|
.done(data => {
|
||||||
|
appendData(repositoryId, itf, data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
const debug = true
|
||||||
|
const Koa = require('koa')
|
||||||
|
const session = require('koa-session')
|
||||||
|
const logger = require('koa-logger')
|
||||||
|
const serve = require('koa-static')
|
||||||
|
const body = require('koa-body')
|
||||||
|
const cors = require('kcors')
|
||||||
|
const router = require('../src/routes')
|
||||||
|
const config = require('../config')
|
||||||
|
|
||||||
|
const app = new Koa()
|
||||||
|
app.counter = { users: {}, mock: 0 }
|
||||||
|
|
||||||
|
app.keys = config.keys
|
||||||
|
app.use(session(config.session, app))
|
||||||
|
if (debug) app.use(logger())
|
||||||
|
app.use(async(ctx, next) => {
|
||||||
|
await next()
|
||||||
|
if (ctx.path === '/favicon.ico') return
|
||||||
|
ctx.session.views = (ctx.session.views || 0) + 1
|
||||||
|
if (ctx.session.fullname) ctx.app.counter.users[ctx.session.fullname] = true
|
||||||
|
})
|
||||||
|
app.use(cors({
|
||||||
|
credentials: true
|
||||||
|
}))
|
||||||
|
app.use(async(ctx, next) => {
|
||||||
|
await next()
|
||||||
|
if (typeof ctx.body === 'object' && ctx.body.data !== undefined) {
|
||||||
|
ctx.type = 'json'
|
||||||
|
// ctx.body.path = ctx.path
|
||||||
|
ctx.body = JSON.stringify(ctx.body, null, 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.use(async(ctx, next) => {
|
||||||
|
await next()
|
||||||
|
if (ctx.request.query.callback) {
|
||||||
|
let body = typeof ctx.body === 'object' ? JSON.stringify(ctx.body, null, 2) : ctx.body
|
||||||
|
ctx.body = ctx.request.query.callback + '(' + body + ')'
|
||||||
|
ctx.type = 'application/x-javascript'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(serve('public'))
|
||||||
|
app.use(serve('test'))
|
||||||
|
app.use(body())
|
||||||
|
app.use(router.routes())
|
||||||
|
|
||||||
|
module.exports = app
|
@ -0,0 +1,19 @@
|
|||||||
|
const start = () => {
|
||||||
|
let execSync = require('child_process').execSync
|
||||||
|
let app = require('./app')
|
||||||
|
let port = 8080
|
||||||
|
let url = `http://localhost:${port}` // /api.html
|
||||||
|
let open = false
|
||||||
|
console.log('----------------------------------------')
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`rap2-dolores is running as ${url}`)
|
||||||
|
if (!open) return
|
||||||
|
try {
|
||||||
|
execSync(`osascript openChrome.applescript ${url}`, { cwd: __dirname, stdio: 'ignore' })
|
||||||
|
} catch (e) {
|
||||||
|
execSync(`open ${url}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
@ -0,0 +1,102 @@
|
|||||||
|
// let Random = require('mockjs').Random
|
||||||
|
const { mock } = require('mockjs')
|
||||||
|
|
||||||
|
const scopes = ['request', 'response']
|
||||||
|
const methods = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
const types = ['String', 'Number', 'Boolean', 'Object', 'Array', 'Function', 'RegExp']
|
||||||
|
const values = ['@INT', '@FLOAT', '@TITLE', '@NAME']
|
||||||
|
|
||||||
|
let USER_ID = 100000000
|
||||||
|
let ORGANIZATION_ID = 1
|
||||||
|
let REPOSITORY_ID = 1
|
||||||
|
let MODULE_ID = 1
|
||||||
|
let INTERFACE_ID = 1
|
||||||
|
let PROPERTY_ID = 1
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BO_ADMIN: { id: USER_ID++, fullname: 'admin', email: 'admin@rap2.com', password: 'admin' },
|
||||||
|
BO_MOZHI: { id: USER_ID++, fullname: '墨智', email: 'mozhi@rap2.com', password: 'mozhi' },
|
||||||
|
BO_USER_COUNT: 10,
|
||||||
|
BO_USER_FN: () => mock({
|
||||||
|
id: USER_ID++,
|
||||||
|
empId: 'bo@natural',
|
||||||
|
fullname: '@cname',
|
||||||
|
email: '@email',
|
||||||
|
password: '@word(6)'
|
||||||
|
}),
|
||||||
|
BO_ORGANIZATION_COUNT: 3,
|
||||||
|
BO_ORGANIZATION_FN: (source) => {
|
||||||
|
return Object.assign(
|
||||||
|
mock({
|
||||||
|
id: ORGANIZATION_ID++,
|
||||||
|
name: '组织@ctitle(5)',
|
||||||
|
description: '@cparagraph',
|
||||||
|
logo: '@url',
|
||||||
|
creatorId: undefined,
|
||||||
|
owner: undefined,
|
||||||
|
members: ''
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
)
|
||||||
|
},
|
||||||
|
BO_REPOSITORY_COUNT: 3,
|
||||||
|
BO_REPOSITORY_FN: (source) => {
|
||||||
|
return Object.assign(
|
||||||
|
mock({
|
||||||
|
id: REPOSITORY_ID++,
|
||||||
|
name: '仓库@ctitle',
|
||||||
|
description: '@cparagraph',
|
||||||
|
logo: '@url'
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
)
|
||||||
|
},
|
||||||
|
BO_MODULE_COUNT: 3,
|
||||||
|
BO_MODULE_FN: (source) => {
|
||||||
|
return Object.assign(
|
||||||
|
mock({
|
||||||
|
id: MODULE_ID++,
|
||||||
|
name: '模块@ctitle(4)',
|
||||||
|
description: '@cparagraph',
|
||||||
|
repositoryId: undefined,
|
||||||
|
creatorId: undefined
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
)
|
||||||
|
},
|
||||||
|
BO_INTERFACE_COUNT: 3,
|
||||||
|
BO_INTERFACE_FN: (source) => {
|
||||||
|
return Object.assign(
|
||||||
|
mock({
|
||||||
|
id: INTERFACE_ID++,
|
||||||
|
name: '接口@ctitle(4)',
|
||||||
|
url: '/@word(5)/@word(5)/@word(5).json',
|
||||||
|
'method|1': methods,
|
||||||
|
description: '@cparagraph',
|
||||||
|
creatorId: undefined,
|
||||||
|
lockerId: undefined,
|
||||||
|
repositoryId: undefined,
|
||||||
|
moduleId: undefined
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
)
|
||||||
|
},
|
||||||
|
BO_PROPERTY_COUNT: 6,
|
||||||
|
BO_PROPERTY_FN: (source) => {
|
||||||
|
return Object.assign(
|
||||||
|
mock({
|
||||||
|
id: PROPERTY_ID++,
|
||||||
|
'scope|1': scopes,
|
||||||
|
name: '@word(6)',
|
||||||
|
'type|1': types,
|
||||||
|
'value|1': values,
|
||||||
|
description: '@csentence',
|
||||||
|
creatorId: undefined,
|
||||||
|
repositoryId: undefined,
|
||||||
|
moduleId: undefined,
|
||||||
|
interfaceId: undefined
|
||||||
|
}),
|
||||||
|
source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
const sequelize = require('../../src/models/sequelize')
|
||||||
|
const { User, Organization, Repository, Module, Interface, Property } = require('../../src/models/index')
|
||||||
|
const { BO_ADMIN, BO_MOZHI } = require('./bo')
|
||||||
|
const { BO_USER_FN, BO_ORGANIZATION_FN, BO_REPOSITORY_FN, BO_MODULE_FN, BO_INTERFACE_FN, BO_PROPERTY_FN } = require('./bo')
|
||||||
|
const { BO_USER_COUNT, BO_ORGANIZATION_COUNT, BO_REPOSITORY_COUNT, BO_MODULE_COUNT, BO_INTERFACE_COUNT, BO_PROPERTY_COUNT } = require('./bo')
|
||||||
|
const EMPTY_WHERE = { where: {} }
|
||||||
|
|
||||||
|
async function init () {
|
||||||
|
await sequelize.drop()
|
||||||
|
await sequelize.sync({
|
||||||
|
force: true,
|
||||||
|
logging: console.log
|
||||||
|
})
|
||||||
|
await User.destroy(EMPTY_WHERE)
|
||||||
|
await Organization.destroy(EMPTY_WHERE)
|
||||||
|
await Repository.destroy(EMPTY_WHERE)
|
||||||
|
await Module.destroy(EMPTY_WHERE)
|
||||||
|
await Interface.destroy(EMPTY_WHERE)
|
||||||
|
await Property.destroy(EMPTY_WHERE)
|
||||||
|
|
||||||
|
// 用户
|
||||||
|
await User.create(BO_ADMIN)
|
||||||
|
await User.create(BO_MOZHI)
|
||||||
|
for (let i = 0; i < BO_USER_COUNT; i++) {
|
||||||
|
await User.create(BO_USER_FN())
|
||||||
|
}
|
||||||
|
|
||||||
|
let users = await User.findAll()
|
||||||
|
|
||||||
|
// 用户 admin 仓库
|
||||||
|
for (let BO_REPOSITORY_INDEX = 0; BO_REPOSITORY_INDEX < BO_REPOSITORY_COUNT; BO_REPOSITORY_INDEX++) {
|
||||||
|
let repository = await Repository.create(
|
||||||
|
BO_REPOSITORY_FN({ creatorId: BO_ADMIN.id, ownerId: BO_ADMIN.id })
|
||||||
|
)
|
||||||
|
await repository.setMembers(
|
||||||
|
users.filter(user => user.id !== BO_ADMIN.id)
|
||||||
|
)
|
||||||
|
await initRepository(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户 mozhi 的仓库
|
||||||
|
for (let BO_REPOSITORY_INDEX = 0; BO_REPOSITORY_INDEX < BO_REPOSITORY_COUNT; BO_REPOSITORY_INDEX++) {
|
||||||
|
let repository = await Repository.create(
|
||||||
|
BO_REPOSITORY_FN({ creatorId: BO_MOZHI.id, ownerId: BO_MOZHI.id })
|
||||||
|
)
|
||||||
|
await repository.setMembers(
|
||||||
|
users.filter(user => user.id !== BO_MOZHI.id)
|
||||||
|
)
|
||||||
|
await initRepository(repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 团队
|
||||||
|
for (let BO_ORGANIZATION_INDEX = 0; BO_ORGANIZATION_INDEX < BO_ORGANIZATION_COUNT; BO_ORGANIZATION_INDEX++) {
|
||||||
|
let organization = await Organization.create(
|
||||||
|
BO_ORGANIZATION_FN({ creatorId: BO_ADMIN.id, ownerId: BO_ADMIN.id })
|
||||||
|
)
|
||||||
|
await organization.setMembers(
|
||||||
|
users.filter(user => user.id !== BO_ADMIN.id)
|
||||||
|
)
|
||||||
|
// 团队的仓库
|
||||||
|
for (let BO_REPOSITORY_INDEX = 0; BO_REPOSITORY_INDEX < BO_REPOSITORY_COUNT; BO_REPOSITORY_INDEX++) {
|
||||||
|
let repository = await Repository.create(
|
||||||
|
BO_REPOSITORY_FN({ creatorId: BO_ADMIN.id, ownerId: BO_ADMIN.id, organizationId: organization.id })
|
||||||
|
)
|
||||||
|
await repository.setMembers(
|
||||||
|
users.filter(user => user.id !== BO_ADMIN.id)
|
||||||
|
)
|
||||||
|
await initRepository(repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initRepository (repository) {
|
||||||
|
// 模块
|
||||||
|
for (let BO_MODULE_INDEX = 0; BO_MODULE_INDEX < BO_MODULE_COUNT; BO_MODULE_INDEX++) {
|
||||||
|
let mod = await Module.create(
|
||||||
|
BO_MODULE_FN({ creatorId: repository.creatorId, repositoryId: repository.id })
|
||||||
|
)
|
||||||
|
await repository.addModule(mod)
|
||||||
|
// 接口
|
||||||
|
for (let BO_INTERFACE_INDEX = 0; BO_INTERFACE_INDEX < BO_INTERFACE_COUNT; BO_INTERFACE_INDEX++) {
|
||||||
|
let itf = await Interface.create(
|
||||||
|
BO_INTERFACE_FN({ creatorId: mod.creatorId, repositoryId: repository.id, moduleId: mod.id })
|
||||||
|
)
|
||||||
|
await mod.addInterface(itf)
|
||||||
|
// 属性
|
||||||
|
for (let BO_PROPERTY_INDEX = 0; BO_PROPERTY_INDEX < BO_PROPERTY_COUNT; BO_PROPERTY_INDEX++) {
|
||||||
|
let prop = await Property.create(
|
||||||
|
BO_PROPERTY_FN({ creatorId: itf.creatorId, repositoryId: repository.id, moduleId: mod.id, interfaceId: itf.id })
|
||||||
|
)
|
||||||
|
await itf.addProperty(prop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function after () {
|
||||||
|
let exclude = ['password', 'createdAt', 'updatedAt', 'deletedAt']
|
||||||
|
let repositories = await Repository.findAll({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
{ model: User, as: 'creator', attributes: { exclude }, required: true },
|
||||||
|
{ model: User, as: 'owner', attributes: { exclude }, required: true },
|
||||||
|
{ model: Organization, as: 'organization', attributes: { exclude }, required: false },
|
||||||
|
{ model: User, as: 'locker', attributes: { exclude }, required: false },
|
||||||
|
{ model: User, as: 'members', attributes: { exclude }, through: { attributes: [] }, required: true },
|
||||||
|
{ model: Module,
|
||||||
|
as: 'modules',
|
||||||
|
attributes: { exclude },
|
||||||
|
// through: { attributes: [] },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Interface,
|
||||||
|
as: 'interfaces',
|
||||||
|
attributes: { exclude },
|
||||||
|
// through: { attributes: [] },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Property,
|
||||||
|
as: 'properties',
|
||||||
|
attributes: { exclude },
|
||||||
|
// through: { attributes: [] },
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
offset: 0,
|
||||||
|
limit: 100
|
||||||
|
})
|
||||||
|
// console.log(JSON.stringify(repositories, null, 2))
|
||||||
|
console.log(repositories.map(item => item.toJSON()))
|
||||||
|
|
||||||
|
let admin = await User.findById(BO_ADMIN.id)
|
||||||
|
// for (let k in admin) console.log(k)
|
||||||
|
let owned = await admin.getOwnedOrganizations()
|
||||||
|
console.log(owned.map(item => item.toJSON()))
|
||||||
|
|
||||||
|
let mozhi = await User.findById(BO_MOZHI.id)
|
||||||
|
for (let k in mozhi) console.log(k)
|
||||||
|
let joined = await mozhi.getJoinedOrganizations()
|
||||||
|
console.log(joined.map(item => item.toJSON()))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init,
|
||||||
|
after
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
const { init, after } = require('./delos')
|
||||||
|
/**
|
||||||
|
* initialize database
|
||||||
|
*/
|
||||||
|
async function main () {
|
||||||
|
await init()
|
||||||
|
await after()
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
@ -0,0 +1,85 @@
|
|||||||
|
(*
|
||||||
|
Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
This source code is licensed under the BSD-style license found in the
|
||||||
|
-- LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*)
|
||||||
|
|
||||||
|
property targetTab: null
|
||||||
|
property targetTabIndex: -1
|
||||||
|
property targetWindow: null
|
||||||
|
|
||||||
|
on run argv
|
||||||
|
set theURL to item 1 of argv
|
||||||
|
|
||||||
|
tell application "Chrome"
|
||||||
|
|
||||||
|
if (count every window) = 0 then
|
||||||
|
make new window
|
||||||
|
end if
|
||||||
|
|
||||||
|
-- 1: Looking for tab running debugger
|
||||||
|
-- then, Reload debugging tab if found
|
||||||
|
-- then return
|
||||||
|
set found to my lookupTabWithUrl(theURL)
|
||||||
|
if found then
|
||||||
|
set targetWindow's active tab index to targetTabIndex
|
||||||
|
tell targetTab to reload
|
||||||
|
tell targetWindow to activate
|
||||||
|
set index of targetWindow to 1
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
|
||||||
|
-- 2: Looking for Empty tab
|
||||||
|
-- In case debugging tab was not found
|
||||||
|
-- We try to find an empty tab instead
|
||||||
|
set found to my lookupTabWithUrl("chrome://newtab/")
|
||||||
|
if found then
|
||||||
|
set targetWindow's active tab index to targetTabIndex
|
||||||
|
set URL of targetTab to theURL
|
||||||
|
tell targetWindow to activate
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
|
||||||
|
-- 3: Create new tab
|
||||||
|
-- both debugging and empty tab were not found
|
||||||
|
-- make a new tab with url
|
||||||
|
tell window 1
|
||||||
|
activate
|
||||||
|
make new tab with properties {URL:theURL}
|
||||||
|
end tell
|
||||||
|
end tell
|
||||||
|
end run
|
||||||
|
|
||||||
|
-- Function:
|
||||||
|
-- Lookup tab with given url
|
||||||
|
-- if found, store tab, index, and window in properties
|
||||||
|
-- (properties were declared on top of file)
|
||||||
|
on lookupTabWithUrl(lookupUrl)
|
||||||
|
tell application "Chrome"
|
||||||
|
-- Find a tab with the given url
|
||||||
|
set found to false
|
||||||
|
set theTabIndex to -1
|
||||||
|
repeat with theWindow in every window
|
||||||
|
set theTabIndex to 0
|
||||||
|
repeat with theTab in every tab of theWindow
|
||||||
|
set theTabIndex to theTabIndex + 1
|
||||||
|
if (theTab's URL as string) contains lookupUrl then
|
||||||
|
-- assign tab, tab index, and window to properties
|
||||||
|
set targetTab to theTab
|
||||||
|
set targetTabIndex to theTabIndex
|
||||||
|
set targetWindow to theWindow
|
||||||
|
set found to true
|
||||||
|
exit repeat
|
||||||
|
end if
|
||||||
|
end repeat
|
||||||
|
|
||||||
|
if found then
|
||||||
|
exit repeat
|
||||||
|
end if
|
||||||
|
end repeat
|
||||||
|
end tell
|
||||||
|
return found
|
||||||
|
end lookupTabWithUrl
|
@ -0,0 +1,305 @@
|
|||||||
|
-- MySQL dump 10.13 Distrib 5.7.12, for osx10.9 (x86_64)
|
||||||
|
--
|
||||||
|
-- Host: localhost Database: RAP2_DELOS_APP_LOCAL
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 5.7.12
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `interfaces`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `interfaces`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `interfaces` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`name` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`url` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`method` varchar(32) NOT NULL COMMENT '-',
|
||||||
|
`description` text COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`moduleId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`creatorId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`lockerId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_moduleId` (`moduleId`),
|
||||||
|
KEY `idx_creatorId` (`creatorId`),
|
||||||
|
KEY `idx_lockerId` (`lockerId`),
|
||||||
|
KEY `idx_repositoryId` (`repositoryId`),
|
||||||
|
CONSTRAINT `interfaces_ibfk_1` FOREIGN KEY (`moduleId`) REFERENCES `modules` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `interfaces_ibfk_2` FOREIGN KEY (`creatorId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `interfaces_ibfk_3` FOREIGN KEY (`lockerId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `interfaces_ibfk_4` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='接口';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `loggers`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `loggers`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `loggers` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`type` varchar(32) NOT NULL COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`userId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`organizationId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`moduleId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`interfaceId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_userId` (`userId`),
|
||||||
|
KEY `idx_repositoryId` (`repositoryId`),
|
||||||
|
KEY `idx_organizationId` (`organizationId`),
|
||||||
|
KEY `idx_moduleId` (`moduleId`),
|
||||||
|
KEY `idx_interfaceId` (`interfaceId`),
|
||||||
|
CONSTRAINT `loggers_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `loggers_ibfk_2` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `loggers_ibfk_3` FOREIGN KEY (`organizationId`) REFERENCES `organizations` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `loggers_ibfk_4` FOREIGN KEY (`moduleId`) REFERENCES `modules` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `loggers_ibfk_5` FOREIGN KEY (`interfaceId`) REFERENCES `interfaces` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='操作日志';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `modules`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `modules`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `modules` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`name` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`description` text COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`creatorId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_repositoryId` (`repositoryId`),
|
||||||
|
KEY `idx_creatorId` (`creatorId`),
|
||||||
|
CONSTRAINT `modules_ibfk_1` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `modules_ibfk_2` FOREIGN KEY (`creatorId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='模块';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `notifications`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `notifications`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `notifications` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`fromId` bigint(11) DEFAULT NULL COMMENT '-',
|
||||||
|
`toId` bigint(11) NOT NULL COMMENT '-',
|
||||||
|
`type` varchar(128) NOT NULL COMMENT '-',
|
||||||
|
`param1` varchar(128) DEFAULT NULL COMMENT '-',
|
||||||
|
`param2` varchar(128) DEFAULT NULL COMMENT '-',
|
||||||
|
`param3` varchar(128) DEFAULT NULL COMMENT '-',
|
||||||
|
`readed` tinyint(1) NOT NULL COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) COMMENT='消息';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `organizations`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `organizations`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `organizations` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`name` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`description` text COMMENT '-',
|
||||||
|
`logo` varchar(256) DEFAULT NULL COMMENT '-',
|
||||||
|
`visibility` tinyint(1) NOT NULL DEFAULT '1' COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`ownerId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`creatorId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_creatorId` (`creatorId`),
|
||||||
|
CONSTRAINT `organizations_ibfk_1` FOREIGN KEY (`creatorId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='团队';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `repositories_collaborators`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `repositories_collaborators`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `repositories_collaborators` (
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
`collaboratorId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`repositoryId`,`collaboratorId`),
|
||||||
|
KEY `idx_collaboratorId` (`collaboratorId`),
|
||||||
|
CONSTRAINT `repositories_collaborators_ibfk_1` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `repositories_collaborators_ibfk_2` FOREIGN KEY (`collaboratorId`) REFERENCES `repositories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) COMMENT='协同仓库';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `organizations_members`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `organizations_members`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `organizations_members` (
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`userId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
`organizationId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`userId`,`organizationId`),
|
||||||
|
KEY `idx_organizationId` (`organizationId`),
|
||||||
|
CONSTRAINT `organizations_members_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `organizations_members_ibfk_2` FOREIGN KEY (`organizationId`) REFERENCES `organizations` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) COMMENT='用户';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `properties`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `properties`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `properties` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`scope` varchar(32) NOT NULL DEFAULT 'response' COMMENT '-',
|
||||||
|
`name` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`type` varchar(32) NOT NULL COMMENT '-',
|
||||||
|
`rule` varchar(128) DEFAULT NULL COMMENT '-',
|
||||||
|
`value` text COMMENT '-',
|
||||||
|
`description` text COMMENT '-',
|
||||||
|
`parentId` bigint(11) NOT NULL DEFAULT '-1' COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`interfaceId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`creatorId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`moduleId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_interfaceId` (`interfaceId`),
|
||||||
|
KEY `idx_creatorId` (`creatorId`),
|
||||||
|
KEY `idx_moduleId` (`moduleId`),
|
||||||
|
KEY `idx_repositoryId` (`repositoryId`),
|
||||||
|
CONSTRAINT `properties_ibfk_1` FOREIGN KEY (`interfaceId`) REFERENCES `interfaces` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `properties_ibfk_2` FOREIGN KEY (`creatorId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `properties_ibfk_3` FOREIGN KEY (`moduleId`) REFERENCES `modules` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `properties_ibfk_4` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='属性';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `repositories`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `repositories`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `repositories` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`name` varchar(256) NOT NULL COMMENT '-',
|
||||||
|
`description` text COMMENT '-',
|
||||||
|
`logo` varchar(256) DEFAULT NULL COMMENT '-',
|
||||||
|
`visibility` tinyint(1) NOT NULL DEFAULT '1' COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
`ownerId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`organizationId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`creatorId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
`lockerId` bigint(11) unsigned DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_ownerId` (`ownerId`),
|
||||||
|
KEY `idx_organizationId` (`organizationId`),
|
||||||
|
KEY `idx_creatorId` (`creatorId`),
|
||||||
|
CONSTRAINT `repositories_ibfk_1` FOREIGN KEY (`ownerId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `repositories_ibfk_2` FOREIGN KEY (`organizationId`) REFERENCES `organizations` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `repositories_ibfk_3` FOREIGN KEY (`creatorId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) COMMENT='仓库';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `repositories_members`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `repositories_members`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `repositories_members` (
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`userId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
`repositoryId` bigint(11) unsigned NOT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`userId`,`repositoryId`),
|
||||||
|
KEY `idx_repositoryId` (`repositoryId`),
|
||||||
|
CONSTRAINT `repositories_members_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `repositories_members_ibfk_2` FOREIGN KEY (`repositoryId`) REFERENCES `repositories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) COMMENT='用户';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `users`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '唯一标识',
|
||||||
|
`fullname` varchar(32) NOT NULL COMMENT '-',
|
||||||
|
`password` varchar(32) DEFAULT NULL COMMENT '-',
|
||||||
|
`email` varchar(128) NOT NULL COMMENT '-',
|
||||||
|
`createdAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`updatedAt` datetime NOT NULL COMMENT '-',
|
||||||
|
`deletedAt` datetime DEFAULT NULL COMMENT '-',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_email` (`email`),
|
||||||
|
UNIQUE KEY `uk_users_email_unique` (`email`)
|
||||||
|
) COMMENT='用户';
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
-- Dump completed on 2017-05-19 23:47:54
|
@ -0,0 +1,21 @@
|
|||||||
|
const start = () => {
|
||||||
|
// https://github.com/node-modules/graceful
|
||||||
|
const graceful = require('graceful')
|
||||||
|
const now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
|
||||||
|
const app = require('./app')
|
||||||
|
const { serve: { port } } = require('../config')
|
||||||
|
const server = app.listen(port, () => {
|
||||||
|
console.log(`[${now()}] worker#${process.pid} rap2-dolores is running as ${port}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
graceful({
|
||||||
|
servers: [server],
|
||||||
|
killTimeout: '10s',
|
||||||
|
error: function (err, throwErrorCount) {
|
||||||
|
if (err.message) err.message += ` (uncaughtException throw ${throwErrorCount} times on pid:${process.pid})`
|
||||||
|
console.error(`[${now()}] worker#${process.pid}] ${err.message}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
@ -0,0 +1,8 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
module.exports = {
|
||||||
|
id: { type: Sequelize.BIGINT(11).UNSIGNED, primaryKey: true, allowNull: false, autoIncrement: true, comment: '唯一标识' },
|
||||||
|
include: [],
|
||||||
|
exclude: {
|
||||||
|
generalities: ['createdAt', 'updatedAt', 'deletedAt', 'reserve']
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
const methods = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
module.exports = sequelize.define('interface', {
|
||||||
|
id,
|
||||||
|
name: { type: Sequelize.STRING(256), allowNull: false, comment: '接口名称' },
|
||||||
|
url: { type: Sequelize.STRING(256), allowNull: false, comment: '接口地址' },
|
||||||
|
method: { type: Sequelize.ENUM(...methods), allowNull: false, comment: '接口类型' },
|
||||||
|
description: { type: Sequelize.TEXT, allowNull: true, comment: '接口描述' },
|
||||||
|
// DONE 2.2 支持接口排序
|
||||||
|
priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '接口'
|
||||||
|
})
|
@ -0,0 +1,12 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
module.exports = sequelize.define('module', {
|
||||||
|
id,
|
||||||
|
name: { type: Sequelize.STRING(256), allowNull: false, comment: '模块名称' },
|
||||||
|
description: { type: Sequelize.TEXT, comment: '模块描述' },
|
||||||
|
priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '模块'
|
||||||
|
})
|
@ -0,0 +1,16 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
module.exports = sequelize.define('notification', {
|
||||||
|
id,
|
||||||
|
fromId: { type: Sequelize.BIGINT(11), allowNull: true, comment: '发送者' },
|
||||||
|
toId: { type: Sequelize.BIGINT(11), allowNull: false, comment: '接受者' },
|
||||||
|
type: { type: Sequelize.STRING(128), allowNull: false, comment: '消息类型' },
|
||||||
|
param1: { type: Sequelize.STRING(128), allowNull: true, comment: '参数1' },
|
||||||
|
param2: { type: Sequelize.STRING(128), allowNull: true, comment: '参数2' },
|
||||||
|
param3: { type: Sequelize.STRING(128), allowNull: true, comment: '参数3' },
|
||||||
|
readed: { type: Sequelize.BOOLEAN, allowNull: false, defautValue: false, comment: '是否已读' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '消息'
|
||||||
|
})
|
@ -0,0 +1,13 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
module.exports = sequelize.define('organization', {
|
||||||
|
id,
|
||||||
|
name: { type: Sequelize.STRING(256), allowNull: false, comment: '团队名称' },
|
||||||
|
description: { type: Sequelize.TEXT, allowNull: true, comment: '团队描述' },
|
||||||
|
logo: { type: Sequelize.STRING(256), allowNull: true, comment: '团队标志' },
|
||||||
|
visibility: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true, comment: '是否公开' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '团队'
|
||||||
|
})
|
@ -0,0 +1,19 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
const SCOPES = ['request', 'response']
|
||||||
|
const TYPES = ['String', 'Number', 'Boolean', 'Object', 'Array', 'Function', 'RegExp']
|
||||||
|
module.exports = sequelize.define('property', {
|
||||||
|
id,
|
||||||
|
scope: { type: Sequelize.ENUM(...SCOPES), allowNull: false, defaultValue: 'response', comment: '属性归属' },
|
||||||
|
name: { type: Sequelize.STRING(256), allowNull: false, comment: '属性名称' },
|
||||||
|
type: { type: Sequelize.ENUM(...TYPES), allowNull: false, comment: '属性值类型' },
|
||||||
|
rule: { type: Sequelize.STRING(128), allowNull: true, comment: '属性值生成规则' },
|
||||||
|
value: { type: Sequelize.TEXT, allowNull: true, comment: '属性值' },
|
||||||
|
description: { type: Sequelize.TEXT, allowNull: true, comment: '属性描述' },
|
||||||
|
parentId: { type: Sequelize.BIGINT(11), allowNull: false, defaultValue: -1, comment: '父属性' },
|
||||||
|
priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '属性'
|
||||||
|
})
|
@ -0,0 +1,13 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
module.exports = sequelize.define('repository', {
|
||||||
|
id,
|
||||||
|
name: { type: Sequelize.STRING(256), allowNull: false, comment: '仓库名称' },
|
||||||
|
description: { type: Sequelize.TEXT, allowNull: true, comment: '仓库描述' },
|
||||||
|
logo: { type: Sequelize.STRING(256), allowNull: true, comment: '仓库标志' },
|
||||||
|
visibility: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true, comment: '是否公开' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '仓库'
|
||||||
|
})
|
@ -0,0 +1,58 @@
|
|||||||
|
// require('colors')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const config = require('../../config')
|
||||||
|
const chalk = require('chalk')
|
||||||
|
const now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
|
||||||
|
const logging = process.env.NODE_ENV === 'development'
|
||||||
|
? (sql) => {
|
||||||
|
sql = sql.replace('Executing (default): ', '')
|
||||||
|
console.log(`${chalk.bold('SQL')} ${now()} ${chalk.gray(sql)}`)
|
||||||
|
}
|
||||||
|
: console.log
|
||||||
|
|
||||||
|
const sequelize = new Sequelize({
|
||||||
|
database: config.db.database,
|
||||||
|
username: config.db.username,
|
||||||
|
password: config.db.password,
|
||||||
|
host: config.db.host,
|
||||||
|
port: config.db.port,
|
||||||
|
dialect: config.db.dialect,
|
||||||
|
pool: config.db.pool,
|
||||||
|
logging: config.db.logging ? logging : false
|
||||||
|
})
|
||||||
|
|
||||||
|
sequelize.authenticate()
|
||||||
|
.then((/* err */) => {
|
||||||
|
// console.log('Connection has been established successfully.');
|
||||||
|
console.log('----------------------------------------')
|
||||||
|
console.log('DATABASE √')
|
||||||
|
console.log(' HOST %s', config.db.host)
|
||||||
|
console.log(' PORT %s', config.db.port)
|
||||||
|
console.log(' DATABASE %s', config.db.database)
|
||||||
|
console.log('----------------------------------------')
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log('Unable to connect to the database:', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = sequelize
|
||||||
|
|
||||||
|
// module.exports = {
|
||||||
|
// Sequelize,
|
||||||
|
// sequelize,
|
||||||
|
// id: { type: Sequelize.BIGINT(11).UNSIGNED, primaryKey: true, allowNull: false, autoIncrement: true },
|
||||||
|
// attributes: {
|
||||||
|
// create_date: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW, comment: '创建时间' },
|
||||||
|
// update_date: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW, comment: '更新时间' },
|
||||||
|
// delete_date: { type: Sequelize.DATE, allowNull: true, comment: '删除时间' },
|
||||||
|
// reserve: { type: Sequelize.STRING, allowNull: true, comment: '备用' }
|
||||||
|
// },
|
||||||
|
// options: {
|
||||||
|
// // freezeTableName: true,
|
||||||
|
// // createdAt: 'create_date',
|
||||||
|
// // updatedAt: 'update_date',
|
||||||
|
// // deletedAt: 'delete_date',
|
||||||
|
// paranoid: true
|
||||||
|
// },
|
||||||
|
// exclude: ['password', 'create_date', 'delete_date', 'reserve'] // 'update_date',
|
||||||
|
// }
|
@ -0,0 +1,12 @@
|
|||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const sequelize = require('./sequelize')
|
||||||
|
const { id } = require('./helper')
|
||||||
|
module.exports = sequelize.define('user', {
|
||||||
|
id,
|
||||||
|
fullname: { type: Sequelize.STRING(32), allowNull: false, comment: '姓名' },
|
||||||
|
password: { type: Sequelize.STRING(32), allowNull: true, comment: '密码' },
|
||||||
|
email: { type: Sequelize.STRING(128), allowNull: false, unique: true, comment: '邮箱' }
|
||||||
|
}, {
|
||||||
|
paranoid: true,
|
||||||
|
comment: '用户'
|
||||||
|
})
|
@ -0,0 +1,219 @@
|
|||||||
|
let router = require('./router')
|
||||||
|
let Pagination = require('./utils/pagination')
|
||||||
|
let { User, Notification, Logger, QueryInclude } = require('../models')
|
||||||
|
|
||||||
|
router.get('/app/get', async (ctx, next) => {
|
||||||
|
let data = {}
|
||||||
|
let query = ctx.query
|
||||||
|
let hooks = {
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
for (let name in hooks) {
|
||||||
|
if (!query[name]) continue
|
||||||
|
data[name] = await hooks[name].findById(query[name], {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: Object.assign({}, ctx.body && ctx.body.data, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account/count', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await User.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account/list', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ fullname: { $like: `%${name}%` } }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let options = { where }
|
||||||
|
let total = await User.count(options)
|
||||||
|
let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 10)
|
||||||
|
ctx.body = {
|
||||||
|
data: await User.findAll(Object.assign(options, {
|
||||||
|
attributes: QueryInclude.User.attributes,
|
||||||
|
offset: pagination.start,
|
||||||
|
limit: pagination.limit,
|
||||||
|
order: [
|
||||||
|
['id', 'DESC']
|
||||||
|
]
|
||||||
|
})),
|
||||||
|
pagination: pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account/info', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: ctx.session.id ? await User.findById(ctx.session.id, {
|
||||||
|
attributes: QueryInclude.User.attributes
|
||||||
|
}) : null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/account/login', async(ctx, next) => {
|
||||||
|
let { email, password } = ctx.request.body
|
||||||
|
let result = await User.findOne({
|
||||||
|
attributes: QueryInclude.User.attributes,
|
||||||
|
where: { email, password }
|
||||||
|
})
|
||||||
|
if (result) {
|
||||||
|
ctx.session.id = result.id
|
||||||
|
ctx.session.fullname = result.fullname
|
||||||
|
ctx.session.email = result.email
|
||||||
|
ctx.app.counter.users[result.fullname] = true
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account/logout', async(ctx, next) => {
|
||||||
|
delete ctx.app.counter.users[ctx.session.email]
|
||||||
|
let id = ctx.session.id
|
||||||
|
Object.assign(ctx.session, { id: undefined, fullname: undefined, email: undefined })
|
||||||
|
ctx.body = {
|
||||||
|
data: await { id }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.post('/account/register', async(ctx, next) => {
|
||||||
|
// TODO 2.4 empId 可能为空,需要重新梳理用户注册流程
|
||||||
|
let { fullname, email, password } = ctx.request.body
|
||||||
|
console.log(fullname)
|
||||||
|
console.log(email)
|
||||||
|
console.log(password)
|
||||||
|
let exists = await User.findAll({
|
||||||
|
where: { email }
|
||||||
|
})
|
||||||
|
if (exists && exists.length) {
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
isOk: false,
|
||||||
|
errMsg: '该邮件已被注册,请更换再试。'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// login automatically after register
|
||||||
|
let result = await User.create({ fullname, email, password })
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
ctx.session.id = result.id
|
||||||
|
ctx.session.fullname = result.fullname
|
||||||
|
ctx.session.email = result.email
|
||||||
|
ctx.app.counter.users[result.fullname] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
id: result.id,
|
||||||
|
fullname: result.fullname,
|
||||||
|
email: result.email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/account/remove', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await User.destroy({
|
||||||
|
where: { id: ctx.query.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO 2.3 账户设置
|
||||||
|
router.get('/account/setting', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/account/setting', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO 2.3 账户通知
|
||||||
|
let NOTIFICATION_EXCLUDE_ATTRIBUTES = []
|
||||||
|
router.get('/account/notification/list', async(ctx, next) => {
|
||||||
|
let total = await Notification.count()
|
||||||
|
let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 10)
|
||||||
|
ctx.body = {
|
||||||
|
data: await Notification.findAll({
|
||||||
|
attributes: { exclude: NOTIFICATION_EXCLUDE_ATTRIBUTES },
|
||||||
|
offset: pagination.start,
|
||||||
|
limit: pagination.limit,
|
||||||
|
order: [
|
||||||
|
['id', 'DESC']
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
pagination: pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/account/notification/unreaded', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/account/notification/unreaded', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/account/notification/read', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO 2.3 账户日志
|
||||||
|
router.get('/account/logger', async(ctx, next) => {
|
||||||
|
let auth = await User.findById(ctx.session.id)
|
||||||
|
let repositories = [...await auth.getOwnedRepositories({}), ...await auth.getJoinedRepositories({})]
|
||||||
|
let organizations = [...await auth.getOwnedOrganizations({}), ...await auth.getJoinedOrganizations({})]
|
||||||
|
|
||||||
|
let where = {
|
||||||
|
$or: [
|
||||||
|
{ userId: ctx.session.id },
|
||||||
|
{ repositoryId: repositories.map(item => item.id) },
|
||||||
|
{ organizationId: organizations.map(item => item.id) }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
let total = await Logger.count({ where })
|
||||||
|
let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
|
||||||
|
let logs = await Logger.findAll({
|
||||||
|
where,
|
||||||
|
attributes: {},
|
||||||
|
include: [
|
||||||
|
Object.assign({}, QueryInclude.Creator, { required: false }),
|
||||||
|
QueryInclude.User,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.Repository,
|
||||||
|
QueryInclude.Module,
|
||||||
|
QueryInclude.Interface
|
||||||
|
],
|
||||||
|
offset: pagination.start,
|
||||||
|
limit: pagination.limit,
|
||||||
|
order: [
|
||||||
|
['id', 'DESC']
|
||||||
|
],
|
||||||
|
paranoid: false
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: logs,
|
||||||
|
pagination: pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,111 @@
|
|||||||
|
const router = require('./router')
|
||||||
|
const moment = require('moment')
|
||||||
|
const Sequelize = require('sequelize')
|
||||||
|
const SELECT = { type: Sequelize.QueryTypes.SELECT }
|
||||||
|
const sequelize = require('../models/sequelize')
|
||||||
|
const YYYY_MM_DD = 'YYYY-MM-DD'
|
||||||
|
|
||||||
|
// 最近 30 天新建仓库数
|
||||||
|
router.get('/app/analytics/repositories/created', async (ctx, next) => {
|
||||||
|
let start = moment().startOf('day').subtract(30, 'days').format(YYYY_MM_DD)
|
||||||
|
let end = moment().startOf('day').format(YYYY_MM_DD)
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
DATE(createdAt) AS label,
|
||||||
|
COUNT(*) as value
|
||||||
|
FROM
|
||||||
|
RAP2_DELOS_APP.repositories
|
||||||
|
WHERE
|
||||||
|
createdAt >= '${start}' AND createdAt <= '${end}'
|
||||||
|
GROUP BY label
|
||||||
|
ORDER BY label ASC;
|
||||||
|
`
|
||||||
|
let result = await sequelize.query(sql, SELECT)
|
||||||
|
result = result.map(item => ({
|
||||||
|
label: moment(item.label).format(YYYY_MM_DD),
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 最近 30 天活跃仓库数
|
||||||
|
router.get('/app/analytics/repositories/updated', async (ctx, next) => {
|
||||||
|
let start = moment().startOf('day').subtract(30, 'days').format(YYYY_MM_DD)
|
||||||
|
let end = moment().startOf('day').format(YYYY_MM_DD)
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
DATE(updatedAt) AS label,
|
||||||
|
COUNT(*) as value
|
||||||
|
FROM
|
||||||
|
RAP2_DELOS_APP.repositories
|
||||||
|
WHERE
|
||||||
|
updatedAt >= '${start}' AND updatedAt <= '${end}'
|
||||||
|
GROUP BY label
|
||||||
|
ORDER BY label ASC;
|
||||||
|
`
|
||||||
|
let result = await sequelize.query(sql, SELECT)
|
||||||
|
result = result.map(item => ({
|
||||||
|
label: moment(item.label).format(YYYY_MM_DD),
|
||||||
|
value: item.value
|
||||||
|
}))
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 最近 30 天活跃用户
|
||||||
|
router.get('/app/analytics/users/activation', async (ctx, next) => {
|
||||||
|
let start = moment().startOf('day').subtract(30, 'days').format(YYYY_MM_DD)
|
||||||
|
let end = moment().startOf('day').format(YYYY_MM_DD)
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
loggers.userId AS userId,
|
||||||
|
users.empId AS empId,
|
||||||
|
users.fullname AS fullname,
|
||||||
|
COUNT(*) AS value
|
||||||
|
FROM
|
||||||
|
loggers
|
||||||
|
LEFT JOIN
|
||||||
|
(users) ON (loggers.userId = users.id)
|
||||||
|
WHERE
|
||||||
|
loggers.updatedAt >= '${start}' AND loggers.updatedat <= '${end}'
|
||||||
|
GROUP BY loggers.userId
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 10
|
||||||
|
`
|
||||||
|
let result = await sequelize.query(sql, SELECT)
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 最近 30 天活跃仓库
|
||||||
|
router.get('/app/analytics/repositories/activation', async (ctx, next) => {
|
||||||
|
let start = moment().startOf('day').subtract(30, 'days').format(YYYY_MM_DD)
|
||||||
|
let end = moment().startOf('day').format(YYYY_MM_DD)
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
loggers.repositoryId AS repositoryId,
|
||||||
|
repositories.name,
|
||||||
|
COUNT(*) AS value
|
||||||
|
FROM
|
||||||
|
loggers
|
||||||
|
LEFT JOIN
|
||||||
|
(repositories) ON (loggers.repositoryId = repositories.id)
|
||||||
|
WHERE
|
||||||
|
loggers.repositoryId IS NOT NULL
|
||||||
|
AND loggers.updatedAt >= '${start}'
|
||||||
|
AND loggers.updatedat <= '${end}'
|
||||||
|
GROUP BY loggers.repositoryId
|
||||||
|
ORDER BY value DESC
|
||||||
|
LIMIT 10
|
||||||
|
`
|
||||||
|
let result = await sequelize.query(sql, SELECT)
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO 2.3 支持 start、end
|
@ -0,0 +1,14 @@
|
|||||||
|
const router = require('./router')
|
||||||
|
const config = require('../../config')
|
||||||
|
|
||||||
|
router.get('/app/counter', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: {
|
||||||
|
version: config.version,
|
||||||
|
users: Object.keys(ctx.app.counter.users).length,
|
||||||
|
mock: ctx.app.counter.mock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,10 @@
|
|||||||
|
let router = require('./router')
|
||||||
|
|
||||||
|
require('./counter')
|
||||||
|
require('./account')
|
||||||
|
require('./organization')
|
||||||
|
require('./repository')
|
||||||
|
require('./mock')
|
||||||
|
require('./analytics')
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,234 @@
|
|||||||
|
const { URL } = require('url')
|
||||||
|
const router = require('./router')
|
||||||
|
const { Repository, Interface, Property, QueryInclude } = require('../models')
|
||||||
|
const attributes = { exclude: [] }
|
||||||
|
const Tree = require('./utils/tree')
|
||||||
|
const pt = require('node-print').pt
|
||||||
|
const beautify = require('js-beautify').js_beautify
|
||||||
|
|
||||||
|
// 检测是否存在重复接口,会在返回的插件 JS 中提示。同时也会在编辑器中提示。
|
||||||
|
const parseDuplicatedInterfaces = (repository) => {
|
||||||
|
let counter = {}
|
||||||
|
for (let itf of repository.interfaces) {
|
||||||
|
let key = `${itf.method} ${itf.url}`
|
||||||
|
counter[key] = [...(counter[key] || []), { id: itf.id, method: itf.method, url: itf.url }]
|
||||||
|
}
|
||||||
|
let duplicated = []
|
||||||
|
for (let key in counter) {
|
||||||
|
if (counter[key].length > 1) {
|
||||||
|
duplicated.push(counter[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return duplicated
|
||||||
|
}
|
||||||
|
const generatePlugin = (protocol, host, repository) => {
|
||||||
|
// DONE 2.3 protocol 错误,应该是 https
|
||||||
|
let duplicated = parseDuplicatedInterfaces(repository)
|
||||||
|
let editor = `${protocol}://rap2.alibaba-inc.com/repository/editor?id=${repository.id}`
|
||||||
|
let result = `
|
||||||
|
/**
|
||||||
|
* 仓库 #${repository.id} ${repository.name}
|
||||||
|
* 在线编辑 ${editor}
|
||||||
|
* 仓库数据 ${protocol}://${host}/repository/get?id=${repository.id}
|
||||||
|
* 请求地址 ${protocol}://${host}/app/mock/${repository.id}/:method/:url
|
||||||
|
* 或者 ${protocol}://${host}/app/mock/template/:interfaceId
|
||||||
|
* 或者 ${protocol}://${host}/app/mock/data/:interfaceId
|
||||||
|
*/
|
||||||
|
;(function(){
|
||||||
|
let repositoryId = ${repository.id}
|
||||||
|
let interfaces = [
|
||||||
|
${repository.interfaces.map(itf =>
|
||||||
|
`{ id: ${itf.id}, name: '${itf.name}', method: '${itf.method}', url: '${itf.url}',
|
||||||
|
request: ${JSON.stringify(itf.request)},
|
||||||
|
response: ${JSON.stringify(itf.response)} }`
|
||||||
|
).join(',\n ')}
|
||||||
|
]
|
||||||
|
${duplicated.length ? `console.warn('检测到重复接口,请访问 ${editor} 修复警告!')\n` : ''}
|
||||||
|
let RAP = window.RAP || {
|
||||||
|
protocol: '${protocol}',
|
||||||
|
host: '${host}',
|
||||||
|
interfaces: {}
|
||||||
|
}
|
||||||
|
RAP.interfaces[repositoryId] = interfaces
|
||||||
|
window.RAP = RAP
|
||||||
|
})();`
|
||||||
|
return beautify(result, { indent_size: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/app/plugin/:repositories', async (ctx, next) => {
|
||||||
|
let repositoryIds = new Set(ctx.params.repositories.split(',').map(item => +item).filter(item => item)) // _.uniq() => Set
|
||||||
|
let result = []
|
||||||
|
for (let id of repositoryIds) {
|
||||||
|
let repository = await Repository.findById(id, {
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
]
|
||||||
|
})
|
||||||
|
if (!repository) continue
|
||||||
|
if (repository.collaborators) {
|
||||||
|
repository.collaborators.map(item => {
|
||||||
|
repositoryIds.add(item.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
console.log(repositoryIds)
|
||||||
|
repository.interfaces = await Interface.findAll({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
where: {
|
||||||
|
repositoryId: repository.id
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
QueryInclude.Properties
|
||||||
|
]
|
||||||
|
})
|
||||||
|
repository.interfaces.forEach(itf => {
|
||||||
|
itf.request = Tree.ArrayToTreeToTemplate(itf.properties.filter(item => item.scope === 'request'))
|
||||||
|
itf.response = Tree.ArrayToTreeToTemplate(itf.properties.filter(item => item.scope === 'response'))
|
||||||
|
})
|
||||||
|
// 修复 协议总是 http
|
||||||
|
// https://lark.alipay.com/login-session/unity-login/xp92ap
|
||||||
|
let protocol = ctx.headers['x-client-scheme'] || ctx.protocol
|
||||||
|
result.push(generatePlugin(protocol, ctx.host, repository))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.type = 'application/x-javascript'
|
||||||
|
ctx.body = result.join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
// /app/mock/:repository/:method/:url
|
||||||
|
// X DONE 2.2 支持 GET POST PUT DELETE 请求
|
||||||
|
// DONE 2.2 忽略请求地址中的前缀斜杠
|
||||||
|
// DONE 2.3 支持所有类型的请求,这样从浏览器中发送跨越请求时不需要修改 method
|
||||||
|
router.all('/app/mock/(\\d+)/(\\w+)/(.+)', async (ctx, next) => {
|
||||||
|
ctx.app.counter.mock++
|
||||||
|
|
||||||
|
let [ repositoryId, method, url ] = [ctx.params[0], ctx.params[1], ctx.params[2]]
|
||||||
|
|
||||||
|
let urlWithoutPrefixSlash = /(\/)?(.*)/.exec(url)[2]
|
||||||
|
let urlWithoutSearch
|
||||||
|
try {
|
||||||
|
let urlParts = new URL(url)
|
||||||
|
urlWithoutSearch = `${urlParts.origin}${urlParts.pathname}`
|
||||||
|
} catch (e) {
|
||||||
|
urlWithoutSearch = url
|
||||||
|
}
|
||||||
|
// console.log([urlWithoutPrefixSlash, '/' + urlWithoutPrefixSlash, urlWithoutSearch])
|
||||||
|
// DONE 2.3 腐烂的 KISSY
|
||||||
|
// KISSY 1.3.2 会把路径中的 // 替换为 /。在浏览器端拦截跨域请求时,需要 encodeURIComponent(url) 以防止 http:// 被替换为 http:/。但是同时也会把参数一起编码,导致 route 的 url 部分包含了参数。
|
||||||
|
// 所以这里重新解析一遍!!!
|
||||||
|
|
||||||
|
let repository = await Repository.findById(repositoryId)
|
||||||
|
let collaborators = await repository.getCollaborators()
|
||||||
|
|
||||||
|
let itf = await Interface.findOne({
|
||||||
|
attributes,
|
||||||
|
where: {
|
||||||
|
repositoryId: [repositoryId, ...collaborators.map(item => item.id)],
|
||||||
|
method,
|
||||||
|
url: [urlWithoutPrefixSlash, '/' + urlWithoutPrefixSlash, urlWithoutSearch]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!itf) {
|
||||||
|
ctx.body = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let interfaceId = itf.id
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope: 'response' }
|
||||||
|
})
|
||||||
|
properties = properties.map(item => item.toJSON())
|
||||||
|
// pt(properties)
|
||||||
|
|
||||||
|
// DONE 2.2 支持引用请求参数
|
||||||
|
let requestProperties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope: 'request' }
|
||||||
|
})
|
||||||
|
requestProperties = requestProperties.map(item => item.toJSON())
|
||||||
|
let requestData = Tree.ArrayToTreeToTemplateToData(requestProperties)
|
||||||
|
Object.assign(requestData, ctx.query)
|
||||||
|
|
||||||
|
let data = Tree.ArrayToTreeToTemplateToData(properties, requestData)
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = JSON.stringify(data, null, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// DONE 2.2 支持获取请求参数的模板、数据、Schema
|
||||||
|
router.get('/app/mock/template/:interfaceId', async (ctx, next) => {
|
||||||
|
ctx.app.counter.mock++
|
||||||
|
let { interfaceId } = ctx.params
|
||||||
|
let { scope = 'response' } = ctx.query
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope }
|
||||||
|
})
|
||||||
|
pt(properties.map(item => item.toJSON()))
|
||||||
|
let template = Tree.ArrayToTreeToTemplate(properties)
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = Tree.stringifyWithFunctonAndRegExp(template)
|
||||||
|
// ctx.body = template
|
||||||
|
// ctx.body = JSON.stringify(template, null, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/app/mock/data/:interfaceId', async (ctx, next) => {
|
||||||
|
ctx.app.counter.mock++
|
||||||
|
let { interfaceId } = ctx.params
|
||||||
|
let { scope = 'response' } = ctx.query
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope }
|
||||||
|
})
|
||||||
|
properties = properties.map(item => item.toJSON())
|
||||||
|
// pt(properties)
|
||||||
|
|
||||||
|
// DONE 2.2 支持引用请求参数
|
||||||
|
let requestProperties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope: 'request' }
|
||||||
|
})
|
||||||
|
requestProperties = requestProperties.map(item => item.toJSON())
|
||||||
|
let requestData = Tree.ArrayToTreeToTemplateToData(requestProperties)
|
||||||
|
Object.assign(requestData, ctx.query)
|
||||||
|
|
||||||
|
let data = Tree.ArrayToTreeToTemplateToData(properties, requestData)
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = JSON.stringify(data, null, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/app/mock/schema/:interfaceId', async (ctx, next) => {
|
||||||
|
ctx.app.counter.mock++
|
||||||
|
let { interfaceId } = ctx.params
|
||||||
|
let { scope = 'response' } = ctx.query
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope }
|
||||||
|
})
|
||||||
|
pt(properties.map(item => item.toJSON()))
|
||||||
|
properties = properties.map(item => item.toJSON())
|
||||||
|
let schema = Tree.ArrayToTreeToTemplateToJSONSchema(properties)
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = Tree.stringifyWithFunctonAndRegExp(schema)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/app/mock/tree/:interfaceId', async (ctx, next) => {
|
||||||
|
ctx.app.counter.mock++
|
||||||
|
let { interfaceId } = ctx.params
|
||||||
|
let { scope = 'response' } = ctx.query
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes,
|
||||||
|
where: { interfaceId, scope }
|
||||||
|
})
|
||||||
|
pt(properties.map(item => item.toJSON()))
|
||||||
|
properties = properties.map(item => item.toJSON())
|
||||||
|
let tree = Tree.ArrayToTree(properties)
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = Tree.stringifyWithFunctonAndRegExp(tree)
|
||||||
|
})
|
@ -0,0 +1,225 @@
|
|||||||
|
let _ = require('underscore')
|
||||||
|
let router = require('./router')
|
||||||
|
let Pagination = require('./utils/pagination')
|
||||||
|
let { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger } = require('../models')
|
||||||
|
|
||||||
|
Organization.hook('afterCreate', async(instance, options) => {
|
||||||
|
await Logger.create({
|
||||||
|
userId: instance.creatorId,
|
||||||
|
type: 'create',
|
||||||
|
organizationId: instance.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/app/get', async (ctx, next) => {
|
||||||
|
let data = {}
|
||||||
|
let query = ctx.query
|
||||||
|
let hooks = {
|
||||||
|
organization: Organization
|
||||||
|
}
|
||||||
|
for (let name in hooks) {
|
||||||
|
if (!query[name]) continue
|
||||||
|
data[name] = await hooks[name].findById(query[name], {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: Object.assign({}, ctx.body && ctx.body.data, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/organization/count', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Organization.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/organization/list', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let total = await Organization.count({
|
||||||
|
where,
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner
|
||||||
|
]
|
||||||
|
})
|
||||||
|
let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
|
||||||
|
let organizations = await Organization.findAll({
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Members
|
||||||
|
],
|
||||||
|
offset: pagination.start,
|
||||||
|
limit: pagination.limit,
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: organizations,
|
||||||
|
pagination: pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/organization/owned', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth = await User.findById(ctx.session.id)
|
||||||
|
let options = {
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
}
|
||||||
|
let owned = await auth.getOwnedOrganizations(options)
|
||||||
|
ctx.body = {
|
||||||
|
data: owned,
|
||||||
|
pagination: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/organization/joined', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth = await User.findById(ctx.session.id)
|
||||||
|
let options = {
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
}
|
||||||
|
let joined = await auth.getJoinedOrganizations(options)
|
||||||
|
// await auth.getOwnedOrganizations()
|
||||||
|
// await auth.getJoinedOrganizations()
|
||||||
|
ctx.body = {
|
||||||
|
data: joined,
|
||||||
|
pagination: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/organization/get', async(ctx, next) => {
|
||||||
|
let organization = await Organization.findById(ctx.query.id, {
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: organization
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/organization/create', async(ctx, next) => {
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
let body = Object.assign({}, ctx.request.body, { creatorId, ownerId: creatorId })
|
||||||
|
let created = await Organization.create(body)
|
||||||
|
if (body.memberIds) {
|
||||||
|
let members = await User.findAll({ where: { id: body.memberIds } })
|
||||||
|
await created.setMembers(members)
|
||||||
|
}
|
||||||
|
let filled = await Organization.findById(created.id, {
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: filled
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/organization/update', async(ctx, next) => {
|
||||||
|
let body = Object.assign({}, ctx.request.body)
|
||||||
|
delete body.creatorId
|
||||||
|
// DONE 2.2 支持转移团队
|
||||||
|
// delete body.ownerId
|
||||||
|
let updated = await Organization.update(body, { where: { id: body.id } })
|
||||||
|
if (body.memberIds) {
|
||||||
|
let reloaded = await Organization.findById(body.id)
|
||||||
|
let members = await User.findAll({ where: { id: body.memberIds } })
|
||||||
|
ctx.prevAssociations = await reloaded.getMembers()
|
||||||
|
await reloaded.setMembers(members)
|
||||||
|
ctx.nextAssociations = await reloaded.getMembers()
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: updated[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
// 团队改
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'update',
|
||||||
|
organizationId: id
|
||||||
|
})
|
||||||
|
// 加入 & 退出
|
||||||
|
if (!ctx.prevAssociations || !ctx.nextAssociations) return
|
||||||
|
let prevIds = ctx.prevAssociations.map(item => item.id)
|
||||||
|
let nextIds = ctx.nextAssociations.map(item => item.id)
|
||||||
|
let joined = _.difference(nextIds, prevIds)
|
||||||
|
let exited = _.difference(prevIds, nextIds)
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
for (let userId of joined) {
|
||||||
|
await Logger.create({ creatorId, userId, type: 'join', organizationId: id })
|
||||||
|
}
|
||||||
|
for (let userId of exited) {
|
||||||
|
await Logger.create({ creatorId, userId, type: 'exit', organizationId: id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/organization/transfer', async(ctx, next) => {
|
||||||
|
let { id, ownerId } = ctx.request.body
|
||||||
|
let body = { ownerId }
|
||||||
|
let result = await Organization.update(body, { where: { id } })
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/organization/remove', async(ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
let result = await Organization.destroy({ where: { id } })
|
||||||
|
let repositories = await Repository.findAll({
|
||||||
|
where: { organizationId: id }
|
||||||
|
})
|
||||||
|
if (repositories.length) {
|
||||||
|
let ids = repositories.map(item => item.id)
|
||||||
|
await Repository.destroy({ where: { id: ids } })
|
||||||
|
await Module.destroy({ where: { repositoryId: ids } })
|
||||||
|
await Interface.destroy({ where: { repositoryId: ids } })
|
||||||
|
await Property.destroy({ where: { repositoryId: ids } })
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let { id } = ctx.query
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'delete',
|
||||||
|
organizationId: id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,700 @@
|
|||||||
|
// TODO 2.1 大数据测试,含有大量模块、接口、属性的仓库
|
||||||
|
const router = require('./router')
|
||||||
|
const _ = require('underscore')
|
||||||
|
const Pagination = require('./utils/pagination')
|
||||||
|
const { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger } = require('../models')
|
||||||
|
const Tree = require('./utils/tree')
|
||||||
|
const { initRepository, initModule } = require('./utils/helper')
|
||||||
|
|
||||||
|
router.get('/app/get', async (ctx, next) => {
|
||||||
|
let data = {}
|
||||||
|
let query = ctx.query
|
||||||
|
let hooks = {
|
||||||
|
repository: Repository,
|
||||||
|
module: Module,
|
||||||
|
interface: Interface,
|
||||||
|
property: Property
|
||||||
|
}
|
||||||
|
for (let name in hooks) {
|
||||||
|
if (!query[name]) continue
|
||||||
|
data[name] = await hooks[name].findById(query[name])
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: Object.assign({}, ctx.body && ctx.body.data, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/repository/count', async(ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Repository.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/repository/list', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name, user, organization } = ctx.query
|
||||||
|
if (user) Object.assign(where, { ownerId: user, organizationId: null })
|
||||||
|
if (organization) Object.assign(where, { organizationId: organization })
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let total = await Repository.count({
|
||||||
|
where,
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker
|
||||||
|
]
|
||||||
|
})
|
||||||
|
let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
|
||||||
|
let repositories = await Repository.findAll({
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
],
|
||||||
|
offset: pagination.start,
|
||||||
|
limit: pagination.limit,
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: repositories,
|
||||||
|
pagination: pagination
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/repository/owned', async(ctx, next) => {
|
||||||
|
let where = {} // { organizationId: null } // 彻底废弃个人仓库&团队仓库概念,一个仓库必须属于某个用户,可选属于某个团队
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth = await User.findById(ctx.query.user || ctx.session.id)
|
||||||
|
// let total = await auth.countOwnedRepositories({ where })
|
||||||
|
// let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
|
||||||
|
let repositories = await auth.getOwnedRepositories({
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
],
|
||||||
|
// offset: pagination.start,
|
||||||
|
// limit: pagination.limit,
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: repositories,
|
||||||
|
pagination: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/repository/joined', async(ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { name } = ctx.query
|
||||||
|
if (name) {
|
||||||
|
Object.assign(where, {
|
||||||
|
$or: [
|
||||||
|
{ name: { $like: `%${name}%` } },
|
||||||
|
{ id: name } // name => id
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth = await User.findById(ctx.query.user || ctx.session.id)
|
||||||
|
// let total = await auth.countJoinedRepositories({ where })
|
||||||
|
// let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
|
||||||
|
let repositories = await auth.getJoinedRepositories({
|
||||||
|
where,
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
],
|
||||||
|
// offset: pagination.start,
|
||||||
|
// limit: pagination.limit,
|
||||||
|
order: [['updatedAt', 'DESC']]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: repositories,
|
||||||
|
pagination: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/repository/get', async(ctx, next) => {
|
||||||
|
let repository = await Repository.findById(ctx.query.id, {
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.RepositoryHierarchy,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
]
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: repository
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/repository/create', async(ctx, next) => {
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
let body = Object.assign({}, ctx.request.body, { creatorId, ownerId: creatorId })
|
||||||
|
let created = await Repository.create(body)
|
||||||
|
if (body.memberIds) {
|
||||||
|
let members = await User.findAll({ where: { id: body.memberIds } })
|
||||||
|
await created.setMembers(members)
|
||||||
|
}
|
||||||
|
if (body.collaboratorIds) {
|
||||||
|
let collaborators = await Repository.findAll({ where: { id: body.collaboratorIds } })
|
||||||
|
await created.setCollaborators(collaborators)
|
||||||
|
}
|
||||||
|
await initRepository(created)
|
||||||
|
ctx.body = {
|
||||||
|
data: await Repository.findById(created.id, {
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
include: [
|
||||||
|
QueryInclude.Creator,
|
||||||
|
QueryInclude.Owner,
|
||||||
|
QueryInclude.Locker,
|
||||||
|
QueryInclude.Members,
|
||||||
|
QueryInclude.Organization,
|
||||||
|
QueryInclude.RepositoryHierarchy,
|
||||||
|
QueryInclude.Collaborators
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'create',
|
||||||
|
repositoryId: ctx.body.data.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/repository/update', async(ctx, next) => {
|
||||||
|
let body = Object.assign({}, ctx.request.body)
|
||||||
|
delete body.creatorId
|
||||||
|
// DONE 2.2 支持转移仓库
|
||||||
|
// delete body.ownerId
|
||||||
|
delete body.organizationId
|
||||||
|
let result = await Repository.update(body, { where: { id: body.id } })
|
||||||
|
if (body.memberIds) {
|
||||||
|
let reloaded = await Repository.findById(body.id)
|
||||||
|
let members = await User.findAll({ where: { id: body.memberIds } })
|
||||||
|
ctx.prevAssociations = await reloaded.getMembers()
|
||||||
|
await reloaded.setMembers(members)
|
||||||
|
ctx.nextAssociations = await reloaded.getMembers()
|
||||||
|
}
|
||||||
|
if (body.collaboratorIds) {
|
||||||
|
let reloaded = await Repository.findById(body.id)
|
||||||
|
let collaborators = await Repository.findAll({ where: { id: body.collaboratorIds } })
|
||||||
|
await reloaded.setCollaborators(collaborators)
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'update',
|
||||||
|
repositoryId: id
|
||||||
|
})
|
||||||
|
// 加入 & 退出
|
||||||
|
if (!ctx.prevAssociations || !ctx.nextAssociations) return
|
||||||
|
let prevIds = ctx.prevAssociations.map(item => item.id)
|
||||||
|
let nextIds = ctx.nextAssociations.map(item => item.id)
|
||||||
|
let joined = _.difference(nextIds, prevIds)
|
||||||
|
let exited = _.difference(prevIds, nextIds)
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
for (let userId of joined) {
|
||||||
|
await Logger.create({ creatorId, userId, type: 'join', repositoryId: id })
|
||||||
|
}
|
||||||
|
for (let userId of exited) {
|
||||||
|
await Logger.create({ creatorId, userId, type: 'exit', repositoryId: id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/repository/transfer', async(ctx, next) => {
|
||||||
|
let { id, ownerId, organizationId } = ctx.request.body
|
||||||
|
let body = {}
|
||||||
|
if (ownerId) body.ownerId = ownerId // 转移给其他用户
|
||||||
|
if (organizationId) {
|
||||||
|
body.organizationId = organizationId // 转移给其他团队,同时转移给该团队拥有者
|
||||||
|
body.ownerId = (await Organization.findById(organizationId)).ownerId
|
||||||
|
}
|
||||||
|
let result = await Repository.update(body, { where: { id } })
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/repository/remove', async(ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
let result = await Repository.destroy({ where: { id } })
|
||||||
|
await Module.destroy({ where: { repositoryId: id } })
|
||||||
|
await Interface.destroy({ where: { repositoryId: id } })
|
||||||
|
await Property.destroy({ where: { repositoryId: id } })
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let { id } = ctx.query
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'delete',
|
||||||
|
repositoryId: id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TOEO 锁定/解锁仓库 待测试
|
||||||
|
router.post('/repository/lock', async (ctx, next) => {
|
||||||
|
let user = ctx.session.id
|
||||||
|
if (!user) {
|
||||||
|
ctx.body = { data: 0 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
let result = await Repository.update({ lockerId: user }, {
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
ctx.body = { data: result[0] }
|
||||||
|
})
|
||||||
|
router.post('/repository/unlock', async (ctx, next) => {
|
||||||
|
if (!ctx.session.id) {
|
||||||
|
ctx.body = { data: 0 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
let result = await Repository.update({ lockerId: null }, {
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
ctx.body = { data: result[0] }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模块
|
||||||
|
router.get('/module/count', async (ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Module.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/module/list', async (ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { repositoryId, name } = ctx.query
|
||||||
|
if (repositoryId) where.repositoryId = repositoryId
|
||||||
|
if (name) where.name = { $like: `%${name}%` }
|
||||||
|
ctx.body = {
|
||||||
|
data: await Module.findAll({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
where
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/module/get', async (ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Module.findById(ctx.query.id, {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/module/create', async (ctx, next) => {
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
let body = Object.assign(ctx.request.body, { creatorId })
|
||||||
|
body.priority = (await Module.count()) + 1
|
||||||
|
let created = await Module.create(body)
|
||||||
|
await initModule(created)
|
||||||
|
ctx.body = {
|
||||||
|
data: await Module.findById(created.id)
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
let mod = ctx.body.data
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'create',
|
||||||
|
repositoryId: mod.repositoryId,
|
||||||
|
moduleId: mod.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/module/update', async (ctx, next) => {
|
||||||
|
let body = ctx.request.body
|
||||||
|
let result = await Module.update(body, {
|
||||||
|
where: { id: body.id }
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let mod = ctx.request.body
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'update',
|
||||||
|
repositoryId: mod.repositoryId,
|
||||||
|
moduleId: mod.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.get('/module/remove', async (ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
let result = await Module.destroy({ where: { id } })
|
||||||
|
await Interface.destroy({ where: { moduleId: id } })
|
||||||
|
await Property.destroy({ where: { moduleId: id } })
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let { id } = ctx.query
|
||||||
|
let mod = await Module.findById(id, { paranoid: false })
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'delete',
|
||||||
|
repositoryId: mod.repositoryId,
|
||||||
|
moduleId: mod.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/module/sort', async (ctx, next) => {
|
||||||
|
let { ids } = ctx.request.body
|
||||||
|
for (let index = 0; index < ids.length; index++) {
|
||||||
|
await Module.update({ priority: index + 1 }, {
|
||||||
|
where: { id: ids[index] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: ids.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
router.get('/interface/count', async (ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Interface.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/interface/list', async (ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { repositoryId, moduleId, name } = ctx.query
|
||||||
|
if (repositoryId) where.repositoryId = repositoryId
|
||||||
|
if (moduleId) where.moduleId = moduleId
|
||||||
|
if (name) where.name = { $like: `%${name}%` }
|
||||||
|
ctx.body = {
|
||||||
|
data: await Interface.findAll({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
where
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/interface/get', async (ctx, next) => {
|
||||||
|
let { id, repositoryId, method, url } = ctx.query
|
||||||
|
|
||||||
|
let itf
|
||||||
|
if (id) {
|
||||||
|
itf = await Interface.findById(id, {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
} else if (repositoryId && method && url) {
|
||||||
|
// 同 /app/mock/:repository/:method/:url
|
||||||
|
let urlWithoutPrefixSlash = /(\/)?(.*)/.exec(url)[2]
|
||||||
|
let repository = await Repository.findById(repositoryId)
|
||||||
|
let collaborators = await repository.getCollaborators()
|
||||||
|
|
||||||
|
itf = await Interface.findOne({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
where: {
|
||||||
|
repositoryId: [repositoryId, ...collaborators.map(item => item.id)],
|
||||||
|
method,
|
||||||
|
url: [urlWithoutPrefixSlash, '/' + urlWithoutPrefixSlash]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
itf = itf.toJSON()
|
||||||
|
|
||||||
|
let scopes = ['request', 'response']
|
||||||
|
for (let i = 0; i < scopes.length; i++) {
|
||||||
|
let properties = await Property.findAll({
|
||||||
|
attributes: { exclude: [] },
|
||||||
|
where: { interfaceId: itf.id, scope: scopes[i] }
|
||||||
|
})
|
||||||
|
properties = properties.map(item => item.toJSON())
|
||||||
|
itf[scopes[i] + 'Properties'] = Tree.ArrayToTree(properties).children
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = Tree.stringifyWithFunctonAndRegExp({
|
||||||
|
data: itf
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/interface/create', async (ctx, next) => {
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
let body = Object.assign(ctx.request.body, { creatorId })
|
||||||
|
body.priority = (await Interface.count()) + 1
|
||||||
|
let created = await Interface.create(body)
|
||||||
|
// await initInterface(created)
|
||||||
|
ctx.body = {
|
||||||
|
data: await Interface.findById(created.id)
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
let itf = ctx.body.data
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'create',
|
||||||
|
repositoryId: itf.repositoryId,
|
||||||
|
moduleId: itf.moduleId,
|
||||||
|
interfaceId: itf.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/interface/update', async (ctx, next) => {
|
||||||
|
let body = ctx.request.body
|
||||||
|
let result = await Interface.update(body, {
|
||||||
|
where: { id: body.id }
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let itf = ctx.request.body
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'update',
|
||||||
|
repositoryId: itf.repositoryId,
|
||||||
|
moduleId: itf.moduleId,
|
||||||
|
interfaceId: itf.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.get('/interface/remove', async (ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
let result = await Interface.destroy({ where: { id } })
|
||||||
|
await Property.destroy({ where: { interfaceId: id } })
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let { id } = ctx.query
|
||||||
|
let itf = await Interface.findById(id, { paranoid: false })
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'delete',
|
||||||
|
repositoryId: itf.repositoryId,
|
||||||
|
moduleId: itf.moduleId,
|
||||||
|
interfaceId: itf.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.post('/interface/lock', async (ctx, next) => {
|
||||||
|
if (!ctx.session.id) {
|
||||||
|
ctx.body = { data: 0 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
let itf = await Interface.findById(id)
|
||||||
|
if (itf.lockerId) { // DONE 2.3 BUG 接口可能被不同的人重复锁定。如果已经被锁定,则忽略。
|
||||||
|
ctx.body = {
|
||||||
|
data: 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await Interface.update({ lockerId: ctx.session.id }, {
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
router.post('/interface/unlock', async (ctx, next) => {
|
||||||
|
if (!ctx.session.id) {
|
||||||
|
ctx.body = { data: 0 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let { id } = ctx.request.body
|
||||||
|
let itf = await Interface.findById(id)
|
||||||
|
if (itf.lockerId !== ctx.session.id) { // DONE 2.3 BUG 接口可能被其他人解锁。如果不是同一个用户,则忽略。
|
||||||
|
ctx.body = { data: 0 }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = await Interface.update({ lockerId: null }, {
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
ctx.body = {
|
||||||
|
data: result[0]
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
router.post('/interface/sort', async (ctx, next) => {
|
||||||
|
let { ids } = ctx.request.body
|
||||||
|
for (let index = 0; index < ids.length; index++) {
|
||||||
|
await Interface.update({ priority: index + 1 }, {
|
||||||
|
where: { id: ids[index] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: ids.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
//
|
||||||
|
router.get('/property/count', async (ctx, next) => {
|
||||||
|
ctx.body = {
|
||||||
|
data: await Property.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/property/list', async (ctx, next) => {
|
||||||
|
let where = {}
|
||||||
|
let { repositoryId, moduleId, interfaceId, name } = ctx.query
|
||||||
|
if (repositoryId) where.repositoryId = repositoryId
|
||||||
|
if (moduleId) where.moduleId = moduleId
|
||||||
|
if (interfaceId) where.interfaceId = interfaceId
|
||||||
|
if (name) where.name = { $like: `%${name}%` }
|
||||||
|
ctx.body = {
|
||||||
|
data: await Property.findAll({ where })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.get('/property/get', async (ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
ctx.body = {
|
||||||
|
data: await Property.findById(id, {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/property/create', async (ctx, next) => {
|
||||||
|
let creatorId = ctx.session.id
|
||||||
|
let body = Object.assign(ctx.request.body, { creatorId })
|
||||||
|
let created = await Property.create(body)
|
||||||
|
ctx.body = {
|
||||||
|
data: await Property.findById(created.id, {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/property/update', async (ctx, next) => {
|
||||||
|
let properties = ctx.request.body // JSON.parse(ctx.request.body)
|
||||||
|
properties = Array.isArray(properties) ? properties : [properties]
|
||||||
|
let result = 0
|
||||||
|
for (let item of properties) {
|
||||||
|
let property = _.pick(item, Object.keys(Property.attributes))
|
||||||
|
let affected = await Property.update(property, {
|
||||||
|
where: { id: property.id }
|
||||||
|
})
|
||||||
|
result += affected[0]
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.post('/properties/update', async (ctx, next) => {
|
||||||
|
let { itf } = ctx.query
|
||||||
|
let properties = ctx.request.body // JSON.parse(ctx.request.body)
|
||||||
|
properties = Array.isArray(properties) ? properties : [properties]
|
||||||
|
|
||||||
|
// 删除不在更新列表中的属性
|
||||||
|
// DONE 2.2 清除幽灵属性:子属性的父属性不存在(原因:前端删除父属性后,没有一并删除后代属性,依然传给了后端)
|
||||||
|
// SELECT * FROM properties WHERE parentId!=-1 AND parentId NOT IN (SELECT id FROM properties)
|
||||||
|
/* 查找和删除脚本
|
||||||
|
SELECT * FROM properties
|
||||||
|
WHERE
|
||||||
|
deletedAt is NULL AND
|
||||||
|
parentId != - 1 AND
|
||||||
|
parentId NOT IN (
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT id FROM properties WHERE deletedAt IS NULL
|
||||||
|
) as p
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
let existingProperties = properties.filter(item => !item.memory)
|
||||||
|
let result = await Property.destroy({
|
||||||
|
where: {
|
||||||
|
id: { $notIn: existingProperties.map(item => item.id) },
|
||||||
|
interfaceId: itf
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 更新已存在的属性
|
||||||
|
for (let item of existingProperties) {
|
||||||
|
let affected = await Property.update(item, {
|
||||||
|
where: { id: item.id }
|
||||||
|
})
|
||||||
|
result += affected[0]
|
||||||
|
}
|
||||||
|
// 插入新增加的属性
|
||||||
|
let newProperties = properties.filter(item => item.memory)
|
||||||
|
let memoryIdsMap = {}
|
||||||
|
for (let item of newProperties) {
|
||||||
|
let created = await Property.create(Object.assign({}, item, {
|
||||||
|
id: null,
|
||||||
|
parentId: -1,
|
||||||
|
priority: item.priority || ((await Property.count()) + 1)
|
||||||
|
}))
|
||||||
|
memoryIdsMap[item.id] = created.id
|
||||||
|
item.id = created.id
|
||||||
|
result += 1
|
||||||
|
}
|
||||||
|
// 同步 parentId
|
||||||
|
for (let item of newProperties) {
|
||||||
|
let parentId = memoryIdsMap[item.parentId] || item.parentId
|
||||||
|
await Property.update({ parentId }, {
|
||||||
|
where: { id: item.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
data: result
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}, async(ctx, next) => {
|
||||||
|
if (ctx.body.data === 0) return
|
||||||
|
let itf = await Interface.findById(ctx.query.itf, {
|
||||||
|
attributes: { exclude: [] }
|
||||||
|
})
|
||||||
|
await Logger.create({
|
||||||
|
userId: ctx.session.id,
|
||||||
|
type: 'update',
|
||||||
|
repositoryId: itf.repositoryId,
|
||||||
|
moduleId: itf.moduleId,
|
||||||
|
interfaceId: itf.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.get('/property/remove', async (ctx, next) => {
|
||||||
|
let { id } = ctx.query
|
||||||
|
ctx.body = {
|
||||||
|
data: await Property.destroy({
|
||||||
|
where: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,35 @@
|
|||||||
|
let Router = require('koa-router')
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
let router = new Router()
|
||||||
|
|
||||||
|
// index
|
||||||
|
router.get('/', (ctx, next) => {
|
||||||
|
ctx.body = 'Hello RAP!'
|
||||||
|
})
|
||||||
|
|
||||||
|
// env
|
||||||
|
router.get('/env', (ctx, next) => {
|
||||||
|
ctx.body = process.env.NODE_ENV
|
||||||
|
})
|
||||||
|
|
||||||
|
// fix preload
|
||||||
|
router.get('/check.node', (ctx, next) => {
|
||||||
|
ctx.body = 'success'
|
||||||
|
})
|
||||||
|
router.get('/status.taobao', (ctx, next) => {
|
||||||
|
ctx.body = 'success'
|
||||||
|
})
|
||||||
|
router.get('/test/test.status', (ctx, next) => {
|
||||||
|
ctx.body = 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// proxy
|
||||||
|
router.get('/proxy', async(ctx, next) => {
|
||||||
|
let { target } = ctx.query
|
||||||
|
console.log(` <=> ${target}`)
|
||||||
|
let json = await fetch(target).then(res => res.json())
|
||||||
|
ctx.type = 'json'
|
||||||
|
ctx.body = json
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -0,0 +1,230 @@
|
|||||||
|
let { Module, Interface, Property } = require('../../models')
|
||||||
|
|
||||||
|
const genExampleModule = (extra) => Object.assign({
|
||||||
|
name: '示例模块',
|
||||||
|
description: '示例模块',
|
||||||
|
creatorId: undefined,
|
||||||
|
repositoryId: undefined
|
||||||
|
}, extra)
|
||||||
|
const genExampleInterface = (extra) => Object.assign({
|
||||||
|
name: '示例接口',
|
||||||
|
url: `/example/${Date.now()}`,
|
||||||
|
method: 'GET',
|
||||||
|
description: '示例接口描述',
|
||||||
|
creatorId: undefined,
|
||||||
|
lockerId: null,
|
||||||
|
moduleId: undefined,
|
||||||
|
repositoryId: undefined
|
||||||
|
}, extra)
|
||||||
|
const genExampleProperty = (extra) => Object.assign({
|
||||||
|
scope: undefined,
|
||||||
|
name: 'foo',
|
||||||
|
type: 'String',
|
||||||
|
rule: '',
|
||||||
|
value: '@ctitle',
|
||||||
|
description: { request: '请求属性示例', response: '响应属性示例' }[extra.scope],
|
||||||
|
parentId: -1,
|
||||||
|
creatorId: undefined,
|
||||||
|
interfaceId: undefined,
|
||||||
|
moduleId: undefined,
|
||||||
|
repositoryId: undefined
|
||||||
|
}, extra)
|
||||||
|
|
||||||
|
// 初始化仓库
|
||||||
|
const initRepository = async (repository) => {
|
||||||
|
let mod = await Module.create(genExampleModule({
|
||||||
|
creatorId: repository.creatorId,
|
||||||
|
repositoryId: repository.id
|
||||||
|
}))
|
||||||
|
await initModule(mod)
|
||||||
|
}
|
||||||
|
// 初始化模块
|
||||||
|
const initModule = async (mod) => {
|
||||||
|
let itf = await Interface.create(genExampleInterface({
|
||||||
|
creatorId: mod.creatorId,
|
||||||
|
moduleId: mod.id,
|
||||||
|
repositoryId: mod.repositoryId
|
||||||
|
}))
|
||||||
|
await initInterface(itf)
|
||||||
|
}
|
||||||
|
// 初始化接口
|
||||||
|
const initInterface = async (itf) => {
|
||||||
|
let { creatorId, repositoryId, moduleId } = itf
|
||||||
|
let interfaceId = itf.id
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'request',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
// TODO 2.1 完整的 Mock 示例:无法模拟所有 Mock 规则
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'string',
|
||||||
|
type: 'String',
|
||||||
|
rule: '1-10',
|
||||||
|
value: '★',
|
||||||
|
description: '字符串属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'number',
|
||||||
|
type: 'Number',
|
||||||
|
rule: '1-100',
|
||||||
|
value: '1',
|
||||||
|
description: '数字属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'boolean',
|
||||||
|
type: 'Boolean',
|
||||||
|
rule: '1-2',
|
||||||
|
value: 'true',
|
||||||
|
description: '布尔属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'regexp',
|
||||||
|
type: 'RegExp',
|
||||||
|
rule: '',
|
||||||
|
value: '/[a-z][A-Z][0-9]/',
|
||||||
|
description: '正则属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'function',
|
||||||
|
type: 'Function',
|
||||||
|
rule: '',
|
||||||
|
value: '() => Math.random()',
|
||||||
|
description: '函数属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
let array = await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'array',
|
||||||
|
type: 'Array',
|
||||||
|
rule: '1-10',
|
||||||
|
value: '',
|
||||||
|
description: '数组属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'foo',
|
||||||
|
type: 'Number',
|
||||||
|
rule: '+1',
|
||||||
|
value: 1,
|
||||||
|
description: '数组元素示例',
|
||||||
|
parentId: array.id,
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'bar',
|
||||||
|
type: 'String',
|
||||||
|
rule: '1-10',
|
||||||
|
value: '★',
|
||||||
|
description: '数组元素示例',
|
||||||
|
parentId: array.id,
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'items',
|
||||||
|
type: 'Array',
|
||||||
|
rule: '',
|
||||||
|
value: `[1, true, 'hello', /\\w{10}/]`,
|
||||||
|
description: '自定义数组元素示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
let object = await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'object',
|
||||||
|
type: 'Object',
|
||||||
|
rule: '',
|
||||||
|
value: '',
|
||||||
|
description: '对象属性示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'foo',
|
||||||
|
type: 'Number',
|
||||||
|
rule: '+1',
|
||||||
|
value: 1,
|
||||||
|
description: '对象属性示例',
|
||||||
|
parentId: object.id,
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'bar',
|
||||||
|
type: 'String',
|
||||||
|
rule: '1-10',
|
||||||
|
value: '★',
|
||||||
|
description: '对象属性示例',
|
||||||
|
parentId: object.id,
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
await Property.create(genExampleProperty({
|
||||||
|
scope: 'response',
|
||||||
|
name: 'placeholder',
|
||||||
|
type: 'String',
|
||||||
|
rule: '',
|
||||||
|
value: '@title',
|
||||||
|
description: '占位符示例',
|
||||||
|
creatorId,
|
||||||
|
repositoryId,
|
||||||
|
moduleId,
|
||||||
|
interfaceId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
genExampleModule,
|
||||||
|
genExampleInterface,
|
||||||
|
initRepository,
|
||||||
|
initModule,
|
||||||
|
initInterface
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
const vm = require('vm')
|
||||||
|
const _ = require('underscore')
|
||||||
|
const Mock = require('mockjs')
|
||||||
|
const { RE_KEY } = require('mockjs/src/mock/constant')
|
||||||
|
|
||||||
|
const Tree = {}
|
||||||
|
|
||||||
|
Tree.ArrayToTree = (list) => {
|
||||||
|
let result = {
|
||||||
|
name: 'root',
|
||||||
|
children: [],
|
||||||
|
depth: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let mapped = {}
|
||||||
|
list.forEach(item => { mapped[item.id] = item })
|
||||||
|
|
||||||
|
function _parseChildren (parentId, children, depth) {
|
||||||
|
for (let id in mapped) {
|
||||||
|
let item = mapped[id]
|
||||||
|
if (typeof parentId === 'function' ? parentId(item.parentId) : item.parentId === parentId) {
|
||||||
|
children.push(item)
|
||||||
|
item.depth = depth + 1
|
||||||
|
item.children = _parseChildren(item.id, [], item.depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
_parseChildren(
|
||||||
|
(parentId) => {
|
||||||
|
// 忽略 parentId 为 0 的根属性(历史遗留),现为 -1
|
||||||
|
if (parentId === -1) return true
|
||||||
|
},
|
||||||
|
result.children,
|
||||||
|
result.depth
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 2.x 和前端重复了
|
||||||
|
Tree.TreeToTemplate = (tree) => {
|
||||||
|
function parse (item, result) {
|
||||||
|
let rule = item.rule ? ('|' + item.rule) : ''
|
||||||
|
let value = item.value
|
||||||
|
switch (item.type) {
|
||||||
|
case 'String':
|
||||||
|
result[item.name + rule] = item.value
|
||||||
|
break
|
||||||
|
case 'Number':
|
||||||
|
if (value === '') value = 1
|
||||||
|
let parsed = parseFloat(value)
|
||||||
|
if (!isNaN(parsed)) value = parsed
|
||||||
|
result[item.name + rule] = value
|
||||||
|
break
|
||||||
|
case 'Boolean':
|
||||||
|
if (value === 'true') value = true
|
||||||
|
if (value === 'false') value = false
|
||||||
|
if (value === '0') value = false
|
||||||
|
value = !!value
|
||||||
|
result[item.name + rule] = value
|
||||||
|
break
|
||||||
|
case 'Function':
|
||||||
|
case 'RegExp':
|
||||||
|
try {
|
||||||
|
result[item.name + rule] = eval('(' + item.value + ')') // eslint-disable-line no-eval
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`TreeToTemplate ${e.message}: ${item.type} { ${item.name}${rule}: ${item.value} }`) // TODO 2.2 怎么消除异常值?
|
||||||
|
result[item.name + rule] = item.value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Object':
|
||||||
|
if (item.value) {
|
||||||
|
try {
|
||||||
|
result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval
|
||||||
|
} catch (e) {
|
||||||
|
result[item.name + rule] = item.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[item.name + rule] = {}
|
||||||
|
item.children.forEach((child) => {
|
||||||
|
parse(child, result[item.name + rule])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Array':
|
||||||
|
if (item.value) {
|
||||||
|
try {
|
||||||
|
result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval
|
||||||
|
} catch (e) {
|
||||||
|
result[item.name + rule] = item.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[item.name + rule] = item.children.length ? [{}] : []
|
||||||
|
item.children.forEach((child) => {
|
||||||
|
parse(child, result[item.name + rule][0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = {}
|
||||||
|
tree.children.forEach((child) => {
|
||||||
|
parse(child, result)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.TemplateToData = (template) => {
|
||||||
|
// 数据模板 template 中可能含有攻击代码,例如死循环,所以在沙箱中生成最终数据
|
||||||
|
// https://nodejs.org/dist/latest-v7.x/docs/api/vm.html
|
||||||
|
const sandbox = { Mock, template, data: {} }
|
||||||
|
const script = new vm.Script('data = Mock.mock(template)')
|
||||||
|
const context = new vm.createContext(sandbox) // eslint-disable-line new-cap
|
||||||
|
try {
|
||||||
|
script.runInContext(context, { timeout: 1000 }) // 每次 Mock.mock() 最多执行 1s
|
||||||
|
// DONE 2.1 __root__
|
||||||
|
let data = sandbox.data
|
||||||
|
let keys = Object.keys(data)
|
||||||
|
if (keys.length === 1 && keys[0] === '__root__') data = data.__root__
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.ArrayToTreeToTemplate = (list) => {
|
||||||
|
let tree = Tree.ArrayToTree(list)
|
||||||
|
let template = Tree.TreeToTemplate(tree)
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.ArrayToTreeToTemplateToData = (list, extra) => {
|
||||||
|
let tree = Tree.ArrayToTree(list)
|
||||||
|
let template = Tree.TreeToTemplate(tree)
|
||||||
|
let data
|
||||||
|
|
||||||
|
if (extra) {
|
||||||
|
// DONE 2.2 支持引用请求参数
|
||||||
|
let keys = Object.keys(template).map(item => item.replace(RE_KEY, '$1'))
|
||||||
|
let extraKeys = _.difference(Object.keys(extra), keys)
|
||||||
|
let scopedData = Tree.TemplateToData(
|
||||||
|
Object.assign({}, _.pick(extra, extraKeys), template)
|
||||||
|
)
|
||||||
|
data = _.pick(scopedData, keys)
|
||||||
|
} else {
|
||||||
|
data = Tree.TemplateToData(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
Tree.ArrayToTreeToTemplateToJSONSchema = (list) => {
|
||||||
|
let tree = Tree.ArrayToTree(list)
|
||||||
|
let template = Tree.TreeToTemplate(tree)
|
||||||
|
let schema = Mock.toJSONSchema(template)
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 2.2 执行 JSON.stringify() 序列化时会丢失正则和函数。需要转为字符串或者函数。
|
||||||
|
// X Function.protytype.toJSON = Function.protytype.toString
|
||||||
|
// X RegExp.protytype.toJSON = RegExp.protytype.toString
|
||||||
|
Tree.stringifyWithFunctonAndRegExp = (json) => {
|
||||||
|
return JSON.stringify(json, (k, v) => {
|
||||||
|
if (typeof v === 'function') return v.toString()
|
||||||
|
if (v !== undefined && v !== null && v.exec) return v.toString()
|
||||||
|
else return v
|
||||||
|
}, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Tree
|
@ -0,0 +1,98 @@
|
|||||||
|
/* global before, after */
|
||||||
|
const Random = require('mockjs').Random
|
||||||
|
module.exports = {
|
||||||
|
mockUsers: () => [{}, {}, {}, {}, {}].map(item => (
|
||||||
|
{
|
||||||
|
fullname: Random.cname(),
|
||||||
|
email: Random.email(),
|
||||||
|
password: Random.word(6)
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
mockRepository: () => (
|
||||||
|
{
|
||||||
|
name: '测试用例_临时_' + Random.ctitle(6) + Math.random(),
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
logo: Random.url()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
prepare: (request, should, users, repository) => {
|
||||||
|
users.forEach((item, index) => {
|
||||||
|
before(done => {
|
||||||
|
request.post('/account/register').send(item).expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
Object.assign(item, res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
before(done => {
|
||||||
|
request.post('/account/login')
|
||||||
|
.send({ email: users[0].email, password: users[0].password })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data } = res.body
|
||||||
|
data.should.be.a('object').have.all.keys({ id: users[0].id, fullname: users[0].fullname, email: users[0].email })
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (repository) {
|
||||||
|
before(done => {
|
||||||
|
request.post('/repository/create')
|
||||||
|
.send(
|
||||||
|
Object.assign(repository, {
|
||||||
|
organizationId: undefined,
|
||||||
|
memberIds: users.slice(2).map(item => item.id)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
Object.assign(repository, res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
after(done => {
|
||||||
|
request.get('/repository/remove')
|
||||||
|
.query({ id: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
after(done => {
|
||||||
|
request.get('/account/logout')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
users.forEach((item, index) => {
|
||||||
|
after(done => {
|
||||||
|
request.get('/account/remove').query({ id: users[index].id }).expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
pagination: ['cursor', 'limit', 'total']
|
||||||
|
},
|
||||||
|
excludes: {
|
||||||
|
user: ['password', 'create_date', 'update_date', 'delete_date', 'reserve'],
|
||||||
|
organization: [],
|
||||||
|
repository: ['create_date', 'update_date', 'delete_date', 'reserve']
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
// clear console
|
||||||
|
process.stdout.write(process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H')
|
@ -0,0 +1,104 @@
|
|||||||
|
/* global describe, it */
|
||||||
|
const app = require('../scripts/app')
|
||||||
|
const request = require('supertest').agent(app.listen())
|
||||||
|
const should = require('chai').should()
|
||||||
|
const Random = require('mockjs').Random
|
||||||
|
|
||||||
|
describe('Account', () => {
|
||||||
|
let user = { fullname: Random.cname(), email: Random.email(), password: Random.word(6) }
|
||||||
|
let validUser = (user) => {
|
||||||
|
user.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
}
|
||||||
|
let validPagination = (pagination) => {
|
||||||
|
pagination.should.be.a('object').contain.all.keys(['cursor', 'limit', 'total'])
|
||||||
|
}
|
||||||
|
it('/account/register', (done) => {
|
||||||
|
request.post('/account/register')
|
||||||
|
.send(user)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
console.log(err)
|
||||||
|
should.not.exist(err)
|
||||||
|
validUser(res.body.data)
|
||||||
|
user.id = res.body.data.id
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/count', (done) => {
|
||||||
|
request.get('/account/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number').above(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/list', (done) => {
|
||||||
|
request.get('/account/list')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data, pagination } = res.body
|
||||||
|
data.should.be.a('array').have.length.above(0)
|
||||||
|
data.forEach(item => {
|
||||||
|
validUser(item)
|
||||||
|
})
|
||||||
|
validPagination(pagination)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/login', done => {
|
||||||
|
request.post('/account/login')
|
||||||
|
.send({ email: user.email, password: user.password })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validUser(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/info', done => {
|
||||||
|
request.get('/account/info')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validUser(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/logout', done => {
|
||||||
|
request.get('/account/logout')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.be.a('object').have.all.keys({ id: user.id })
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/info', done => {
|
||||||
|
request.get('/account/info')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
should.not.exist(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/account/remove', (done) => {
|
||||||
|
request.get('/account/remove')
|
||||||
|
.query({ id: user.id })
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,24 @@
|
|||||||
|
/* global describe, it */
|
||||||
|
const app = require('../scripts/app')
|
||||||
|
const request = require('supertest').agent(app.listen())
|
||||||
|
const should = require('chai').should()
|
||||||
|
const { mockUsers, prepare } = require('./helper')
|
||||||
|
|
||||||
|
describe('Counter', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
prepare(request, should, users)
|
||||||
|
|
||||||
|
it('/app/counter', done => {
|
||||||
|
request.get('/app/counter')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { version, users, mock } = res.body.data
|
||||||
|
version.should.be.a('string').not.eq('')
|
||||||
|
users.should.be.a('number').above(0)
|
||||||
|
mock.should.be.a('number')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,128 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
let app = require('../scripts/app')
|
||||||
|
let request = require('supertest').agent(app.listen())
|
||||||
|
let should = require('chai').should()
|
||||||
|
let Random = require('mockjs').Random
|
||||||
|
const { Interface } = require('../src/models')
|
||||||
|
const { mockUsers, mockRepository, prepare } = require('./helper')
|
||||||
|
|
||||||
|
describe('Interface', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
let repository = mockRepository()
|
||||||
|
prepare(request, should, users, repository)
|
||||||
|
|
||||||
|
let itf = {}
|
||||||
|
before(done => {
|
||||||
|
itf = {
|
||||||
|
name: '测试用例_临时_' + Random.ctitle(6) + Math.random(),
|
||||||
|
url: Random.url(),
|
||||||
|
method: Random.pick(['GET', 'POST', 'PUT', 'DELETE']),
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
lockerId: null,
|
||||||
|
repositoryId: repository.id,
|
||||||
|
moduleId: repository.modules[0].id
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
let validInterface = (itf, extras = []) => {
|
||||||
|
itf.should.be.a('object').have.all.keys(
|
||||||
|
Object.keys(Interface.attributes).concat(extras)
|
||||||
|
)
|
||||||
|
itf.creatorId.should.be.a('number')
|
||||||
|
itf.repositoryId.should.be.a('number')
|
||||||
|
itf.moduleId.should.be.a('number')
|
||||||
|
}
|
||||||
|
|
||||||
|
it('/interface/create', done => {
|
||||||
|
request.post('/interface/create')
|
||||||
|
.send(itf)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validInterface(res.body.data)
|
||||||
|
itf = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/count', done => {
|
||||||
|
request.get('/interface/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number').above(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/list', done => {
|
||||||
|
request.get('/interface/list')
|
||||||
|
.query({ moduleId: repository.modules[0].id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 2)
|
||||||
|
data.forEach(item => {
|
||||||
|
validInterface(item)
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/get', done => {
|
||||||
|
request.get('/interface/get')
|
||||||
|
.query({ id: 1 })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validInterface(res.body.data, ['requestProperties', 'responseProperties'])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/update', done => {
|
||||||
|
request.post('/interface/update')
|
||||||
|
.send({ id: itf.id, name: Random.ctitle(6) })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/lock', done => {
|
||||||
|
request.post('/interface/lock')
|
||||||
|
.send({ id: itf.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/unlock', done => {
|
||||||
|
request.post('/interface/unlock')
|
||||||
|
.send({ id: itf.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/interface/remove', done => {
|
||||||
|
request.get('/interface/remove')
|
||||||
|
.query({ id: itf.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,36 @@
|
|||||||
|
/* global describe, it */
|
||||||
|
let app = require('../scripts/app')
|
||||||
|
let request = require('supertest').agent(app.listen())
|
||||||
|
let should = require('chai').should()
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('/', (done) => {
|
||||||
|
request.get('/')
|
||||||
|
.expect('Content-Type', /html/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/check.node', (done) => {
|
||||||
|
request.get('/check.node')
|
||||||
|
.expect('Content-Type', /text/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.text.should.eq('success')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/status.taobao', (done) => {
|
||||||
|
request.get('/status.taobao')
|
||||||
|
.expect('Content-Type', /text/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.text.should.eq('success')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,90 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
let app = require('../scripts/app')
|
||||||
|
let request = require('supertest').agent(app.listen())
|
||||||
|
let should = require('chai').should()
|
||||||
|
const { mockUsers, mockRepository, prepare } = require('./helper')
|
||||||
|
|
||||||
|
describe('Mock', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
let repository = mockRepository()
|
||||||
|
prepare(request, should, users, repository)
|
||||||
|
|
||||||
|
let interfaces
|
||||||
|
before(done => {
|
||||||
|
request.get('/interface/list')
|
||||||
|
.query({ repositoryId: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
interfaces = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/app/plugin/:repository', done => {
|
||||||
|
request.get(`/app/plugin/${repository.id}`)
|
||||||
|
.expect('Content-Type', /javascript/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/app/plugin/:repository,:repository', done => {
|
||||||
|
request.get(`/app/plugin/${repository.id},${repository.id}`)
|
||||||
|
.expect('Content-Type', /javascript/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/app/mock/:repository/:method/:url', done => {
|
||||||
|
request.get(`/app/mock/${interfaces[0].repositoryId}/${interfaces[0].method}/${interfaces[0].url}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/app/mock/template/:interfaceId', done => {
|
||||||
|
request.get(`/app/mock/template/${interfaces[0].id}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/app/mock/data/:interfaceId', done => {
|
||||||
|
request.get(`/app/mock/data/${interfaces[0].id}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
it('/app/get', done => {
|
||||||
|
request.get('/app/get')
|
||||||
|
.query({ user: 100000000, organization: 1, repository: 1, module: 1, interface: 1, property: 1 })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { user, organization, repository, property } = res.body.data
|
||||||
|
let mod = res.body.data.module
|
||||||
|
let itf = res.body.data.interface
|
||||||
|
user.should.be.a('object')
|
||||||
|
organization.should.be.a('object')
|
||||||
|
repository.should.be.a('object')
|
||||||
|
mod.should.be.a('object')
|
||||||
|
itf.should.be.a('object')
|
||||||
|
property.should.be.a('object')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
})
|
@ -0,0 +1,101 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
let app = require('../scripts/app')
|
||||||
|
let request = require('supertest').agent(app.listen())
|
||||||
|
let should = require('chai').should()
|
||||||
|
let Random = require('mockjs').Random
|
||||||
|
const { Module } = require('../src/models')
|
||||||
|
const { mockUsers, mockRepository, prepare } = require('./helper')
|
||||||
|
|
||||||
|
describe('Module', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
let repository = mockRepository()
|
||||||
|
prepare(request, should, users, repository)
|
||||||
|
|
||||||
|
let mod = {}
|
||||||
|
before(done => {
|
||||||
|
mod = {
|
||||||
|
name: Random.ctitle(6),
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
repositoryId: repository.id
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
let validModule = (mod) => {
|
||||||
|
mod.should.be.a('object').have.all.keys(
|
||||||
|
Object.keys(Module.attributes)
|
||||||
|
)
|
||||||
|
mod.creatorId.should.be.a('number')
|
||||||
|
mod.repositoryId.should.be.a('number')
|
||||||
|
}
|
||||||
|
|
||||||
|
it('/module/create', done => {
|
||||||
|
request.post('/module/create')
|
||||||
|
.send(mod)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validModule(res.body.data)
|
||||||
|
mod = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/module/count', done => {
|
||||||
|
request.get('/module/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number').above(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/module/list', done => {
|
||||||
|
request.get('/module/list')
|
||||||
|
.query({ repositoryId: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 2)
|
||||||
|
data.forEach(item => {
|
||||||
|
validModule(item)
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/module/get', done => {
|
||||||
|
request.get('/module/get')
|
||||||
|
.query({ id: mod.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validModule(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/module/update', done => {
|
||||||
|
request.post('/module/update')
|
||||||
|
.send(Object.assign({}, mod, { name: Random.ctitle(6) + Math.random() }))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/module/remove', done => {
|
||||||
|
request.get('/module/remove')
|
||||||
|
.query({ id: mod.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,136 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
const app = require('../scripts/app')
|
||||||
|
const request = require('supertest').agent(app.listen())
|
||||||
|
const should = require('chai').should()
|
||||||
|
const Random = require('mockjs').Random
|
||||||
|
const { Organization } = require('../src/models')
|
||||||
|
const { mockUsers, prepare, keys } = require('./helper')
|
||||||
|
|
||||||
|
describe('Organization', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
prepare(request, should, users)
|
||||||
|
|
||||||
|
let organization
|
||||||
|
before(done => {
|
||||||
|
organization = {
|
||||||
|
name: Random.ctitle(6) + Math.random(),
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
logo: Random.url(),
|
||||||
|
memberIds: users.slice(2).map(item => item.id)
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
let validOrganization = (organization) => {
|
||||||
|
organization.should.be.a('object').have.all.keys(
|
||||||
|
[...Object.keys(Organization.attributes), 'creator', 'owner', 'members']
|
||||||
|
)
|
||||||
|
let { creator, owner, members } = organization
|
||||||
|
creator.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
owner.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
members.should.be.a('array').have.length.within(3, 3)
|
||||||
|
members.forEach((user, index) => {
|
||||||
|
owner.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let validPagination = (pagination) => {
|
||||||
|
pagination.should.be.a('object').contain.all.keys(keys.pagination)
|
||||||
|
}
|
||||||
|
it('/organization/create', done => {
|
||||||
|
request.post('/organization/create')
|
||||||
|
.send(organization)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validOrganization(res.body.data)
|
||||||
|
organization = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/count', done => {
|
||||||
|
request.get('/organization/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number').above(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/list', done => {
|
||||||
|
request.get('/organization/list')
|
||||||
|
.query({ name: organization.name, cursor: 1, limit: 1 })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data, pagination } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 1)
|
||||||
|
data.forEach(item => {
|
||||||
|
validOrganization(item)
|
||||||
|
})
|
||||||
|
validPagination(pagination)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/owned', done => {
|
||||||
|
request.get('/organization/owned')
|
||||||
|
.query({ name: organization.name })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data, pagination } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 1)
|
||||||
|
data.forEach(item => {
|
||||||
|
validOrganization(item)
|
||||||
|
})
|
||||||
|
should.not.exist(pagination)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/get', done => {
|
||||||
|
request.get('/organization/get')
|
||||||
|
.query({ id: organization.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validOrganization(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/update', done => {
|
||||||
|
request.post('/organization/update')
|
||||||
|
.send(Object.assign({}, organization, { name: Random.ctitle(6) + Math.random() }))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/transfer', done => {
|
||||||
|
request.post('/organization/transfer')
|
||||||
|
.send({ id: organization.id, ownerId: users[1].id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/organization/remove', done => {
|
||||||
|
request.get('/organization/remove')
|
||||||
|
.query({ id: organization.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,113 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
let app = require('../scripts/app')
|
||||||
|
let request = require('supertest').agent(app.listen())
|
||||||
|
let should = require('chai').should()
|
||||||
|
let Random = require('mockjs').Random
|
||||||
|
const { Property } = require('../src/models')
|
||||||
|
const { mockUsers, mockRepository, prepare } = require('./helper')
|
||||||
|
|
||||||
|
describe('Property', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
let repository = mockRepository()
|
||||||
|
prepare(request, should, users, repository)
|
||||||
|
|
||||||
|
let mod = {}
|
||||||
|
let itf = {}
|
||||||
|
let property = {}
|
||||||
|
before(done => {
|
||||||
|
mod = repository.modules[0]
|
||||||
|
itf = mod.interfaces[0]
|
||||||
|
property = {
|
||||||
|
scope: Random.pick(['request', 'response']),
|
||||||
|
name: Random.word(6),
|
||||||
|
type: Random.pick(['String', 'Number', 'Boolean', 'Object', 'Array', 'Function', 'RegExp']),
|
||||||
|
rule: '',
|
||||||
|
value: Random.pick(['@INT', '@FLOAT', '@TITLE', '@NAME']),
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
parentId: -1,
|
||||||
|
repositoryId: repository.id,
|
||||||
|
moduleId: mod.id,
|
||||||
|
interfaceId: itf.id
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
let validProperty = (property) => {
|
||||||
|
property.should.be.a('object').have.all.keys(
|
||||||
|
Object.keys(Property.attributes)
|
||||||
|
)
|
||||||
|
property.creatorId.should.be.a('number')
|
||||||
|
property.repositoryId.should.be.a('number')
|
||||||
|
property.moduleId.should.be.a('number')
|
||||||
|
}
|
||||||
|
it('/property/create', done => {
|
||||||
|
request.post('/property/create')
|
||||||
|
.send(property)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validProperty(res.body.data)
|
||||||
|
property = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('/property/count', done => {
|
||||||
|
request.get('/property/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/property/list', done => {
|
||||||
|
request.get('/property/list')
|
||||||
|
.query({ interfaceId: itf.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 15)
|
||||||
|
data.forEach(item => {
|
||||||
|
validProperty(item)
|
||||||
|
})
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/property/get', done => {
|
||||||
|
request.get('/property/get')
|
||||||
|
.query({ id: property.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validProperty(res.body.data)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/property/update', done => {
|
||||||
|
request.post('/property/update')
|
||||||
|
.send({ id: property.id, name: Random.word(6) })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/property/remove', done => {
|
||||||
|
request.get('/property/remove')
|
||||||
|
.query({ id: property.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,145 @@
|
|||||||
|
/* global describe, it, before */
|
||||||
|
const app = require('../scripts/app')
|
||||||
|
const request = require('supertest').agent(app.listen())
|
||||||
|
const should = require('chai').should()
|
||||||
|
const Random = require('mockjs').Random
|
||||||
|
const { Repository } = require('../src/models')
|
||||||
|
const { mockUsers, prepare, keys } = require('./helper')
|
||||||
|
|
||||||
|
describe('Repository', () => {
|
||||||
|
let users = mockUsers()
|
||||||
|
prepare(request, should, users)
|
||||||
|
|
||||||
|
let repository = {}
|
||||||
|
before(done => {
|
||||||
|
repository = {
|
||||||
|
name: `测试用例_临时仓库_${Random.ctitle(6)}_${Date.now()}`,
|
||||||
|
description: Random.cparagraph(),
|
||||||
|
logo: Random.url(),
|
||||||
|
organizationId: undefined,
|
||||||
|
memberIds: users.slice(2).map(item => item.id)
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
let validRepository = (repository, deep) => {
|
||||||
|
repository.should.be.a('object').have.all.keys(
|
||||||
|
[...Object.keys(Repository.attributes), 'creator', 'owner', 'members', 'locker', 'organization', 'collaborators']
|
||||||
|
.concat(deep ? ['modules'] : [])
|
||||||
|
)
|
||||||
|
let { creator, owner, members } = repository
|
||||||
|
creator.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
owner.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
members.should.be.a('array').have.length.within(3, 3)
|
||||||
|
members.forEach((user, index) => {
|
||||||
|
owner.should.be.a('object').have.all.keys(['id', 'fullname', 'email'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let validPagination = (pagination) => {
|
||||||
|
pagination.should.be.a('object').contain.all.keys(keys.pagination)
|
||||||
|
}
|
||||||
|
|
||||||
|
it('/repository/create', done => {
|
||||||
|
request.post('/repository/create')
|
||||||
|
.send(repository)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validRepository(res.body.data, true)
|
||||||
|
repository = res.body.data
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/count', done => {
|
||||||
|
request.get('/repository/count')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.to.be.a('number').above(0)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/list', done => {
|
||||||
|
request.get('/repository/list')
|
||||||
|
.query({ name: repository.name, cursor: 1, limit: 1 })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
let { data, pagination } = res.body
|
||||||
|
data.should.be.a('array').have.length.within(1, 1)
|
||||||
|
data.forEach(item => {
|
||||||
|
validRepository(item)
|
||||||
|
})
|
||||||
|
validPagination(pagination)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/get', done => {
|
||||||
|
request.get('/repository/get')
|
||||||
|
.query({ id: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
validRepository(res.body.data, true)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/update', done => {
|
||||||
|
request.post('/repository/update')
|
||||||
|
.send(Object.assign({}, repository, { name: `测试用例_临时仓库_${Random.ctitle(6)}_${Date.now()}` }))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/lock', done => {
|
||||||
|
request.post('/repository/lock')
|
||||||
|
.send({ id: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/unlock', done => {
|
||||||
|
request.post('/repository/unlock')
|
||||||
|
.send({ id: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/transfer', done => {
|
||||||
|
request.post('/repository/transfer')
|
||||||
|
.send({ id: repository.id, ownerId: users[1].id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('/repository/remove', done => {
|
||||||
|
request.get('/repository/remove')
|
||||||
|
.query({ id: repository.id })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end((err, res) => {
|
||||||
|
should.not.exist(err)
|
||||||
|
res.body.data.should.eq(1)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue