first submit

pull/19/head
Bosn 7 years ago
commit 6d64344c31

6
.gitignore vendored

@ -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,6 @@
# 1. 你可以直接编辑本文件的内容或者通过工具来帮你校验合法性和自动生成请点击http://aliwing.alibaba-inc.com/apprelease/home.htm
# 2. 更多关于Release文件的规范和约定请点击: http://docs.alibaba-inc.com/pages/viewpage.action?pageId=252891532
# 构建源码语言类型
code.language=nodejs

@ -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,110 @@
// TODO 2.2 如何缓存重复查询https://github.com/rfink/sequelize-redis-cache
const Helper = require('./helper')
const User = require('./user')
const Organization = require('./organization')
const Repository = require('./repository')
const Module = require('./module')
const Interface = require('./interface')
const Property = require('./property')
const Logger = require('./logger')
const Notification = require('./notification')
// http://docs.sequelizejs.com/manual/tutorial/associations.html
User.OwnedOrganizations = User.hasMany(Organization, { foreignKey: 'ownerId', constraints: false, as: 'ownedOrganizations' })
User.JoinedOrganizations = User.belongsToMany(Organization, { through: 'organizations_members', as: 'joinedOrganizations' })
User.OwnedRepositories = User.hasMany(Repository, { foreignKey: 'ownerId', constraints: false, as: 'ownedRepositories' })
User.JoinedRepositories = User.belongsToMany(Repository, { through: 'repositories_members', as: 'joinedRepositories' })
Organization.Creator = Organization.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者
Organization.Owner = Organization.belongsTo(User, { foreignKey: 'ownerId', constraints: false, as: 'owner' }) // 所有者
Organization.Members = Organization.belongsToMany(User, { through: 'organizations_members', as: 'members' }) // 团队成员
Organization.Repositories = Organization.hasMany(Repository, { foreignKey: 'organizationId', constraints: false, as: 'repositories' })
Repository.Creator = Repository.belongsTo(User, { foreignKey: 'creatorId', constraints: false, as: 'creator' }) // 创建者
Repository.Owner = Repository.belongsTo(User, { foreignKey: 'ownerId', constraints: false, as: 'owner' }) // 所有者
Repository.Organization = Repository.belongsTo(Organization, { foreignKey: 'organizationId', constraints: false, as: 'organization' }) // 所属团队
Repository.Locker = Repository.belongsTo(User, { foreignKey: 'lockerId', constraints: false, as: 'locker' }) // 锁定者
Repository.Members = Repository.belongsToMany(User, { through: 'repositories_members', as: 'members' }) // 仓库成员
Repository.Modules = Repository.hasMany(Module, { foreignKey: 'repositoryId', constraints: false, as: 'modules' })
Repository.Interfaces = Repository.hasMany(Interface, { foreignKey: 'repositoryId', constraints: false, as: 'interfaces' })
Repository.Collaborators = Repository.belongsToMany(Repository, { through: 'repositories_collaborators', as: 'collaborators' }) // 仓库共享
Module.Creator = Module.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者
Module.Repository = Module.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' })
Module.Interfaces = Module.hasMany(Interface, { foreignKey: 'moduleId', constraints: false, as: 'interfaces' })
Interface.Creator = Interface.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者
Interface.Locker = Interface.belongsTo(User, { foreignKey: 'lockerId', as: 'locker' }) // 锁定者
Interface.Module = Interface.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' })
Interface.Repository = Interface.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' })
Interface.Properties = Interface.hasMany(Property, { foreignKey: 'interfaceId', constraints: false, as: 'properties' })
Property.Creator = Property.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者
Property.Interface = Property.belongsTo(Interface, { foreignKey: 'interfaceId', as: 'interface' })
Property.Module = Property.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' })
Property.Repository = Property.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' })
Logger.Creator = Logger.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' })
Logger.User = Logger.belongsTo(User, { foreignKey: 'userId', as: 'user' })
Logger.Repository = Logger.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' })
Logger.Organization = Logger.belongsTo(Organization, { foreignKey: 'organizationId', as: 'organization' })
Logger.Module = Logger.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' })
Logger.Interface = Logger.belongsTo(Interface, { foreignKey: 'interfaceId', as: 'interface' })
const QueryInclude = {
User: { model: User, as: 'user', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, required: true },
Creator: { model: User, as: 'creator', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, required: true },
Owner: { model: User, as: 'owner', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, required: true },
Locker: { model: User, as: 'locker', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, required: false },
Members: { model: User, as: 'members', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, through: { attributes: [] }, required: false },
Repository: { model: Repository, as: 'repository', attributes: { exclude: [] }, paranoid: false, required: false },
Organization: { model: Organization, as: 'organization', attributes: { exclude: [] }, paranoid: false, required: false },
Module: { model: Module, as: 'module', attributes: { exclude: [] }, paranoid: false, required: false },
Interface: { model: Interface, as: 'interface', attributes: { exclude: [] }, paranoid: false, required: false },
Collaborators: { model: Repository, as: 'collaborators', attributes: { exclude: [] }, through: { attributes: [] }, required: false },
RepositoryHierarchy: {
model: Module,
as: 'modules',
attributes: { exclude: [] },
required: false,
separate: true,
order: [
['priority', 'ASC']
],
include: [{
model: Interface,
as: 'interfaces',
attributes: { exclude: [] },
required: false,
separate: true,
order: [
['priority', 'ASC']
],
include: [{
model: User,
as: 'locker',
attributes: { exclude: ['password', ...Helper.exclude.generalities] },
required: false
}, {
model: Property,
as: 'properties',
attributes: { exclude: [] },
required: false,
separate: true
}]
}]
},
Properties: {
model: Property,
as: 'properties',
attributes: { exclude: [] },
required: false
}
}
module.exports = {
User, Organization, Repository, Module, Interface, Property, Logger, Notification, Helper, QueryInclude
}

@ -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,18 @@
const Sequelize = require('sequelize')
const sequelize = require('./sequelize')
const { id } = require('./helper')
const types = ['create', 'update', 'delete', 'lock', 'unlock', 'join', 'exit']
// DONE 2.3 需要加 creator 吗Xxx 把 Xxx 加入了 仓库Xxx 或 团队Xxx。
module.exports = sequelize.define('logger', {
id,
type: { type: Sequelize.ENUM(...types), allowNull: false, comment: '操作类型' },
creatorId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '创建者' },
userId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, comment: '涉及用户' },
organizationId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及组织' },
repositoryId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及仓库' },
moduleId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及模块' },
interfaceId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, 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,148 @@
/*
Pagination
Pure paging implementation reference.
纯粹的分页参考实现
属性
data 数据
total 总条数
cursor 当前页数第几页 1 开始计算
limit 分页大小
pages 总页数
start 当前页的起始下标
end 当前页的结束下标
hasPrev 是否有前一页
hasNext 是否有下一页
hasFirst 是否有第一页
hasLast 是否有最后一页
prev 前一页
next 后一页
first 第一页
last 最后一页
focus 当前页的当前焦点下标
方法
calc() 计算分页状态当属性值发生变化时方法 calc() 被调用
moveTo(cursor) 移动到指定页
moveToPrev() 移动到前一页
moveToNext() 移动到下一页
moveToFirst() 移动到第一页
moveToLast() 移动到最后一页
fetch(arr) 获取当前页的数据或者用当前状态获取参数 arr 的子集
setData(data) 更新数据集合
setTotal(total) 更新总条数
setCursor(cursor) 更新当前页数
setFocus(focus) 设置当前焦点
setLimit(limit) 设置分页大小
get(focus) 获取一条数据
toString() 友好打印
toHTML(url) 生成分页栏
*/
/*
new Pagination( data, cursor, limit )
new Pagination( total, cursor, limit )
*/
function Pagination (data, cursor, limit) {
this.data = (typeof data === 'number' || typeof data === 'string') ? undefined : data
this.total = this.data ? this.data.length : parseInt(data, 10)
this.cursor = parseInt(cursor, 10)
this.limit = parseInt(limit, 10)
this.calc()
}
Pagination.prototype = {
calc: function () {
if (this.total && parseInt(this.total, 10) > 0) {
this.limit = this.limit < 1 ? 1 : this.limit
this.pages = (this.total % this.limit === 0) ? this.total / this.limit : this.total / this.limit + 1
this.pages = parseInt(this.pages, 10)
this.cursor = (this.cursor > this.pages) ? this.pages : this.cursor
this.cursor = (this.cursor < 1) ? this.pages > 0 ? 1 : 0 : this.cursor
this.start = (this.cursor - 1) * this.limit
this.start = (this.start < 0) ? 0 : this.start // 从 0 开始计数
this.end = (this.start + this.limit > this.total) ? this.total : this.start + this.limit
this.end = (this.total < this.limit) ? this.total : this.end
this.hasPrev = (this.cursor > 1)
this.hasNext = (this.cursor < this.pages)
this.hasFirst = this.hasPrev
this.hasLast = this.hasNext
this.prev = this.hasPrev ? this.cursor - 1 : 0
this.next = this.hasNext ? this.cursor + 1 : 0
this.first = this.hasFirst ? 1 : 0
this.last = this.hasLast ? this.pages : 0
this.focus = this.focus ? this.focus : 0
this.focus = this.focus % this.limit + this.start
this.focus = this.focus > this.end - 1 ? this.end - 1 : this.focus
} else {
this.pages = this.cursor = this.start = this.end = 0
this.hasPrev = this.hasNext = this.hasFirst = this.hasLast = false
this.prev = this.next = this.first = this.last = 0
this.focus = 0
}
return this
},
moveTo: function (cursor) {
this.cursor = parseInt(cursor, 10)
return this.calc()
},
moveToPrev: function () {
return this.moveTo(this.cursor - 1)
},
moveToNext: function () {
return this.moveTo(this.cursor + 1)
},
moveToFirst: function () {
return this.moveTo(1)
},
moveToLast: function () {
return this.moveTo(this.pages)
},
fetch: function (arr) {
return (arr || this.data).slice(this.start, this.end)
},
setData: function (data) {
this.data = data
this.total = data.length
return this.calc()
},
setTotal: function (total) {
this.total = parseInt(total, 10)
return this.calc()
},
setCursor: function (cursor) {
this.cursor = parseInt(cursor, 10)
return this.calc()
},
setFocus: function (focus) {
this.focus = parseInt(focus, 10)
if (this.focus < 0) this.focus += this.total
if (this.focus >= this.total) this.focus -= this.total
this.cursor = parseInt(this.focus / this.limit, 10) + 1
return this.calc()
},
setLimit: function (limit) {
this.limit = parseInt(limit, 10)
return this.calc()
},
get: function (focus) {
if (focus !== undefined) return this.data[focus % this.data.length]
else return this.data[this.focus]
},
toString: function () {
return JSON.stringify(this, null, 4)
}
}
Pagination.prototype.to = Pagination.prototype.moveTo
Pagination.prototype.toPrev = Pagination.prototype.moveToPrev
Pagination.prototype.toNext = Pagination.prototype.moveToNext
Pagination.prototype.toFirst = Pagination.prototype.moveToFirst
Pagination.prototype.toLast = Pagination.prototype.moveToLast
module.exports = Pagination

@ -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…
Cancel
Save