Showing preview only (341K chars total). Download the full file or copy to clipboard to get everything.
Repository: thx/rap2-delos
Branch: master
Commit: 05e6bddb7125
Files: 122
Total size: 313.3 KB
Directory structure:
gitextract_mkcp7_61/
├── .dockerignore
├── .github/
│ └── ISSUE_TEMPLATE/
│ ├── ---------.md
│ └── bug--.md
├── .gitignore
├── .jshintrc
├── .prettierrc
├── .travis.yml
├── .vscode/
│ └── launch.json
├── APP-META/
│ └── docker-config/
│ └── environment/
│ └── cai/
│ └── conf/
│ └── nginx-proxy.conf
├── Dockerfile
├── LICENSE
├── README.md
├── database/
│ ├── history/
│ │ ├── patch-null-type.sql
│ │ ├── v2.5_change.sql
│ │ ├── v2.6_change_20180705.sql
│ │ ├── v2.8.0_token.sql
│ │ ├── v2018_07_27.sql
│ │ ├── v2018_08_27_add_property_required.sql
│ │ └── v2019_09_26_default_val_table.sql
│ ├── recreate_db.sql
│ ├── v2018_05_15.sql
│ ├── v2020_07_06_history_log.sql
│ ├── v2020_07_21_body_option_save.sql
│ └── v2020_07_21_merge_PR.sql
├── docker-compose.yml
├── docs/
│ └── Schedule.md
├── package.json
├── public/
│ ├── 404.json
│ ├── 500.json
│ ├── error.html
│ ├── index.html
│ ├── libs/
│ │ ├── README.md
│ │ ├── fetch.rap.js
│ │ ├── jquery.rap.js
│ │ └── mock.rap.js
│ └── test/
│ ├── index.html
│ ├── test.plugin.fetch.html
│ ├── test.plugin.jquery.html
│ ├── test.plugin.mock.html
│ └── test.request.js
├── release/
│ └── v2.6_20180705.md
├── src/
│ ├── config/
│ │ ├── config.dev.ts
│ │ ├── config.local.ts
│ │ ├── config.prod.ts
│ │ └── index.ts
│ ├── dispatch.ts
│ ├── helpers/
│ │ ├── dedent.ts
│ │ └── pandoc.ts
│ ├── models/
│ │ ├── bo/
│ │ │ ├── defaultVal.ts
│ │ │ ├── historyLog.ts
│ │ │ ├── interface.ts
│ │ │ ├── logger.ts
│ │ │ ├── module.ts
│ │ │ ├── notification.ts
│ │ │ ├── organization.ts
│ │ │ ├── organizationsMembers.ts
│ │ │ ├── property.ts
│ │ │ ├── repositoriesCollaborators.ts
│ │ │ ├── repositoriesMembers.ts
│ │ │ ├── repository.ts
│ │ │ └── user.ts
│ │ ├── helper.ts
│ │ ├── index.ts
│ │ ├── sequelize.ts
│ │ └── util/
│ │ ├── helper.ts
│ │ └── queryInclude.ts
│ ├── routes/
│ │ ├── account.ts
│ │ ├── analytics.ts
│ │ ├── base.ts
│ │ ├── counter.ts
│ │ ├── export.ts
│ │ ├── index.ts
│ │ ├── migration.ts
│ │ ├── mock.ts
│ │ ├── organization.ts
│ │ ├── repository.ts
│ │ ├── router.ts
│ │ └── utils/
│ │ ├── access.ts
│ │ ├── const.ts
│ │ ├── helper.ts
│ │ ├── pagination.ts
│ │ ├── tree.ts
│ │ └── url.ts
│ ├── scripts/
│ │ ├── app.ts
│ │ ├── dev.ts
│ │ ├── init/
│ │ │ ├── bo.ts
│ │ │ ├── delos.ts
│ │ │ └── index.ts
│ │ ├── initSchema.ts
│ │ ├── openChrome.applescript
│ │ ├── rap2_delos.sql
│ │ ├── setToken.ts
│ │ ├── updateSchema.ts
│ │ └── worker.ts
│ ├── service/
│ │ ├── export/
│ │ │ ├── docx.ts
│ │ │ ├── markdown.ts
│ │ │ ├── pdf.ts
│ │ │ └── postman.ts
│ │ ├── mail.ts
│ │ ├── migrate.ts
│ │ ├── mock.ts
│ │ ├── organization.ts
│ │ ├── redis.ts
│ │ ├── repository.ts
│ │ ├── task.ts
│ │ └── utils.ts
│ └── types/
│ ├── custom-typings.d.ts
│ ├── index.d.ts
│ └── postman.d.ts
├── test/
│ ├── helper.js
│ ├── index.js
│ ├── test.account.js
│ ├── test.counter.js
│ ├── test.interface.js
│ ├── test.js
│ ├── test.mock.js
│ ├── test.module.js
│ ├── test.organization.js
│ ├── test.property.js
│ └── test.repository.js
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# css
src/**/*.css
build
build.zip
================================================
FILE: .github/ISSUE_TEMPLATE/---------.md
================================================
---
name: 产品功能需求及建议
about: 为RAP2提供新功能的建议
---
**您提出的功能是否和您遇到的问题有关,请描述该问题**
**您是否有建议的实现方案**
**其它帮助我们理解您需求的描述、截图**
================================================
FILE: .github/ISSUE_TEMPLATE/bug--.md
================================================
---
name: Bug反馈
about: 为了更快速的定位您的问题,请提供详细的BUG描述。
---
**BUG描述**
**复现步骤**
**期望结果**
**实际结果**
**截图**
**环境**
* 是否是自建服务器?如果为自建,请提供操作系统版本
* 如果是前端错误,请提供浏览器版本
* 其它可能帮助我们排查问题的环境信息
**附加信息**
================================================
FILE: .gitignore
================================================
.nyc_output
/dist
/bin
.DS_Store
node_modules
bower_components
coverage
npm-debug.log
tmp
package-lock.json
dump.rdb
/docker/mysql/volume
.idea
================================================
FILE: .jshintrc
================================================
{
// 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
}
================================================
FILE: .prettierrc
================================================
{
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
================================================
FILE: .travis.yml
================================================
language: node_js
services:
- mysql
- redis-server
cache:
directories:
- node_modules
- $HOME/.npm
notifications:
email: false
node_js:
- '10.1.0'
before_install:
- npm i -g npm@^5.5.1
- mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci'
script:
- npm install
- npm run build
- npm run create-db
- npm run test
- npm run check
after_success:
================================================
FILE: .vscode/launch.json
================================================
{
// 使用 IntelliSense 以学习相关的 Node.js 调试属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"protocol": "inspector"
}
]
}
================================================
FILE: APP-META/docker-config/environment/cai/conf/nginx-proxy.conf
================================================
server {
listen 80 default_server;
server_name www.taobao.com;
location / {
proxy_pass http://127.0.0.1:6001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
error_page 400 403 405 408 410 411 412 413 414 415 /error.html;
error_page 501 502 503 506 /error.html;
error_page 404 /404.json;
error_page 500 /500.json;
}
server {
listen 80;
server_name status.taobao.com;
tmd off;
location = /nginx_status {
stub_status on;
}
}
================================================
FILE: Dockerfile
================================================
# BUILDING
FROM node:lts-alpine AS builder
# base on work of llitfkitfk@gmail.com
LABEL maintainer="chibing.fy@alibaba-inc.com"
WORKDIR /app
# cache dependencies
COPY package.json ./
# 在国内打开下面一行加速
#RUN npm config set registry https://registry.npm.taobao.org/
# instal dependencies
RUN npm install typescript -g && \
npm install
# build
COPY . ./
RUN npm run build
# RUNNING
FROM node:lts-alpine
# base on work of llitfkitfk@gmail.com
LABEL maintainer="chibing.fy@alibaba-inc.com"
# use China mirror of: https://github.com/jgm/pandoc/releases/download/2.7.3/pandoc-2.7.3-linux.tar.gz
RUN wget http://rap2-taobao-org.oss-cn-beijing.aliyuncs.com/pandoc-2.7.3-linux.tar.gz && \
tar -xf pandoc-2.7.3-linux.tar.gz && \
cp pandoc-2.7.3/bin/* /usr/bin/ && \
pandoc -v && \
rm -rf pandoc-2.7.3-linux.tar.gz pandoc-2.7.3
WORKDIR /app
COPY --from=builder /app/public .
COPY --from=builder /app/dist .
COPY --from=builder /app/node_modules ./node_modules
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 THX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# RAP2-DELOS 开源社区版本 (后端 API 服务器)
**尊敬的用户:**
由于近期安全审查发现数据风险严重隐患,经慎重评估,我们决定对 rap2.taobao.org 官方体验系统进行下线处理。
> [!WARNING]
> **下线时间:2025年11月30日 24:00**
请各位用户在此之前完成以下操作:
- 导出所需的 Mock 数据和配置
- 私有化搭建 rap2 系统到自己的服务器
- 迁移相关数据到自己的系统
- 更新项目中的接口调用地址
**给您带来的不便,敬请谅解。**
> 阿里妈妈 THX 团队新项目 GoGoCode:https://github.com/thx/gogocode 给批量修改项目代码减轻痛苦!
RAP2 是在 RAP1 基础上重做的新项目,它能给你提供方便的接口文档管理、Mock、导出等功能,包含两个组件(对应两个 Github Repository)。
- rap2-delos: 后端数据 API 服务器,基于 Koa + MySQL[link](http://github.com/thx/rap2-delos)
- rap2-dolores: 前端静态资源,基于 React [link](http://github.com/thx/rap2-dolores)
**Rap 官方服务站点,无需安装直接体验: [rap2.taobao.org](http://rap2.taobao.org)**
注意:本工具为开发工具,相关API未做任何XSS等安全验证,请勿在生产环境依赖RAP的任何服务!!!
**有急事来官方钉钉群,响应更迅速: 31626736 (二群,一群已满)**
2019-10-31:现已支持 Docker 一键部署,欢迎大家体验&反馈
2019-09-27:更新的用户请注意按照下面指引安装 pandoc 以启用文档导出功能
## 推荐使用 Docker 快速部署
### 安装 Docker
国内用户可参考 [https://get.daocloud.io/](https://get.daocloud.io/) 安装 Docker 以及 Docker Compose (Linux 用户需要单独安装),建议按照链接指引配置 Docker Hub 的国内镜像提高加载速度。
### 配置项目
在任意地方建立目录 rap
把本仓库中的 [docker-compose.yml](https://raw.githubusercontent.com/thx/rap2-delos/master/docker-compose.yml) 放到 rap 目录中
Rap 前端服务的端口号默认为 3000,你可以在 docker-compose.yml 中按照注释自定义
在 rap 目录下执行下面的命令:
```sh
# 拉取镜像并启动
docker-compose up -d
# 启动后,第一次运行需要手动初始化mysql数据库
# ⚠️注意: 只有第一次该这样做
docker-compose exec delos node scripts/init
# 部署成功后 访问
http://localhost:3000 # 前端(可自定义端口号)
http://localhost:38080 # 后端
# 如果访问不了可能是数据库没有链接上,关闭 rap 服务
docker-compose down
# 再重新运行
docker-compose up -d
# 如果 Sequelize 报错可能是数据库表发生了变化,运行下面命令同步
docker-compose exec delos node scripts/updateSchema
```
**⚠️注意:第一次运行后 rap 目录下会被自动创建一个 docker 目录,里面存有 rap 的数据库数据,可千万不要删除。**
### 镜像升级
Rap 经常会进行 bugfix 和功能升级,用 Docker 可以很方便地跟随主项目升级
```sh
# 拉取一下最新的镜像
docker-compose pull
# 暂停当前应用
docker-compose down
# 重新构建并启动
docker-compose up -d --build
# 有时表结构会发生变化,执行下面命令同步
docker-compose exec delos node scripts/updateSchema
# 清空不被使用的虚悬镜像
docker image prune -f
```
## 手动部署
### 环境要求
- Node.js 8.9.4+
- MySQL 5.7+
- Redis 4.0+
- pandoc 2.73 (供文档生成使用)
### 开发模式
#### 安装 MySQL 和 Redis 服务器
请自行查找搭建方法,mysql/redis 配置在 config.\*.ts 文件中,在不修改任何配置的情况下,
redis 会通过默认端口 + 本机即可正常访问,确保 redis-server 打开即可。
注意:修改 cofig 文件后需要重新 `npm run build` 才能生效
#### 安装 pandoc
我们使用 pandoc 来生成 Rap 的离线文档,安装 Pandoc 最通用的办法是在 pandoc 的 [release 页面](https://github.com/jgm/pandoc/releases/tag/2.7.3)下载对应平台的二进制文件安装即可。
其中 linux 版本最好放在`/usr/local/bin/pandoc` 让终端能直接找到,并执行 `chmod +x /usr/local/bin/pandoc` 给调用权限。
测试在命令行执行命令 `pandoc -h` 有响应即可。
#### 启动redis-server
```sh
redis-server
```
后台执行可以使用 nohup 或 pm2,这里推荐使用 pm2,下面命令会安装 pm2,并通过 pm2 来启动 redis 缓存服务
```bash
npm install -g pm2
npm run start:redis
```
#### 先创建创建数据库
```bash
mysql -e 'CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP DEFAULT CHARSET utf8 COLLATE utf8_general_ci'
```
#### 初始化
```bash
npm install
```
confirm configurations in /config/config.dev.js (used in development mode),确认/config/config.dev.js 中的配置(.dev.js 后缀表示用于开发模式)。
#### 安装 && TypeScript 编译
```bash
npm install -g typescript
npm run build
```
#### 初始化数据库表
```bash
npm run create-db
```
#### 执行 mocha 测试用例和 js 代码规范检查
```bash
npm run check
```
#### 启动开发模式的服务器 监视并在发生代码变更时自动重启
```bash
npm run dev
```
### 生产模式
```sh
# 1. 修改/config/config.prod.js中的服务器配置
# 2. 启动生产模式服务器
npm start
```
## 社区贡献
- [rap2-javabean 自动从 Rap 接口生成 Java Bean](https://github.com/IndiraFinish/rap2-javabean)
- [rap2-generator 把 Java Bean 生成到 Rap](https://github.com/kings1990/rap2-generator)
- [yapix 一键生成接口文档, 上传到yapi, rap2, eolinker等](https://github.com/jetplugins/yapix)
## Author
- 版权: 阿里妈妈前端团队
- 作者:
- RAP2 2017/10 前版本作者为[墨智(@Nuysoft)](https://github.com/nuysoft/), [mockjs](mockjs.com)的作者。
- 2017/10 之后版本开发者
- [霍雍(Bosn)](http://github.com/bosn/),[RAP1](http://github.com/thx/RAP)作者,RAP 最早的创始人。
- [承虎(alvarto)](http://github.com/alvarto/)
- [池冰(bigfengyu)](https://github.com/bigfengyu)
### Tech Arch
- 前端架构(rap2-dolores)
- React / Redux / Saga / Router
- Mock.js
- SASS / Bootstrap 4 beta
- server: nginx
- 后端架构(rap2-delos)
- Koa
- Sequelize
- MySQL
- Server
- server: node
### 旧版本升级
-数据库数据迁移 RAP2 2.4迁移到2.8
由于数据库表有主外键,按以下顺序插入数据
1.Users
2.Organizations
3.Repositories
4.repositories_members(备注:将createdAt、updatedAt两个字段必填去除)
5.organizations_members
6.Modules
7.Interfaces
8.Loggers
9.Properties(备注:将数据scope字段的所有''值替换成'String')
default_val和repositories_collaborators表无数据无需处理
================================================
FILE: database/history/patch-null-type.sql
================================================
ALTER TABLE `Properties`
MODIFY COLUMN `type` enum('String','Number','Boolean','Object','Array','Function','RegExp','Null') NOT NULL;
================================================
FILE: database/history/v2.5_change.sql
================================================
ALTER TABLE repositories_collaborators
DROP COLUMN createdat ;
ALTER TABLE repositories_collaborators
DROP COLUMN updatedat ;
ALTER TABLE repositories_members
DROP COLUMN createdat ;
ALTER TABLE repositories_members
DROP COLUMN updatedat ;
================================================
FILE: database/history/v2.6_change_20180705.sql
================================================
ALTER TABLE `properties`
ADD COLUMN `pos` INT(10) NULL DEFAULT 2;
ALTER TABLE `interfaces`
ADD COLUMN `status` INT(10) NULL DEFAULT 200;
================================================
FILE: database/history/v2.8.0_token.sql
================================================
# 给 Repositories 表添加 token 列
# 2019-11-11
ALTER TABLE Repositories
ADD COLUMN token VARCHAR(32) NULL;
================================================
FILE: database/history/v2018_07_27.sql
================================================
ALTER TABLE Interfaces
MODIFY COLUMN `method` VARCHAR(256) NOT NULL;
================================================
FILE: database/history/v2018_08_27_add_property_required.sql
================================================
ALTER TABLE `Properties`
ADD COLUMN `required` TINYINT(1) NOT NULL DEFAULT 0;
================================================
FILE: database/history/v2019_09_26_default_val_table.sql
================================================
CREATE TABLE `default_val` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`createdat` datetime NOT NULL COMMENT '创建时间',
`updatedat` datetime NOT NULL COMMENT '修改时间',
`name` varchar(256) NOT NULL COMMENT '名字',
`rule` varchar(512) NOT NULL COMMENT '规则',
`value` text NOT NULL COMMENT '值',
`repositoryid` bigint unsigned NOT NULL COMMENT 'FK',
`deletedat` datetime NOT NULL COMMENT '删除时间',
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET=utf8mb4 COMMENT='默认值';
================================================
FILE: database/recreate_db.sql
================================================
DROP DATABASE RAP2_DELOS_APP;
CREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP
DEFAULT CHARSET utf8
COLLATE utf8_general_ci;
================================================
FILE: database/v2018_05_15.sql
================================================
ALTER TABLE Interfaces
MODIFY priority BIGINT(11) NOT NULL DEFAULT 1;
ALTER TABLE Properties
MODIFY priority BIGINT(11) NOT NULL DEFAULT 1;
ALTER TABLE Modules
MODIFY priority BIGINT(11) NOT NULL DEFAULT 1;
================================================
FILE: database/v2020_07_06_history_log.sql
================================================
CREATE TABLE `history_log` (
`id` int NOT NULL AUTO_INCREMENT,
`entityType` int NOT NULL,
`entityId` int NOT NULL,
`changeLog` text COLLATE utf8mb4_unicode_ci NOT NULL,
`relatedJSONData` text COLLATE utf8mb4_unicode_ci,
`userId` bigint(11) unsigned NOT NULL,
`createdAt` datetime NOT NULL,
`updatedAt` datetime NOT NULL,
`deletedAt` datetime DEFAULT NULL,
CONSTRAINT `hisotry_log_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `Users` (`id`) ON UPDATE CASCADE,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
================================================
FILE: database/v2020_07_21_body_option_save.sql
================================================
ALTER TABLE `interfaces`
ADD COLUMN `bodyOption` VARCHAR(255) NULL;
================================================
FILE: database/v2020_07_21_merge_PR.sql
================================================
ALTER TABLE `RAP2_DELOS_APP`.`Properties`
MODIFY COLUMN `scope` enum('request','response','script') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
DEFAULT 'response' COMMENT 'property owner' AFTER `id`;
================================================
FILE: docker-compose.yml
================================================
# mail@dongguochao.com
# llitfkitfk@gmail.com
# chibing.fy@alibaba-inc.com
version: "3"
services:
# frontend
dolores:
image: rapteam/rap2-dolores:latest
ports:
#冒号前可以自定义前端端口号,冒号后不要动
- 3000:38081
# backend
delos:
image: rapteam/rap2-delos:latest
ports:
# 这里的配置不要改哦
- 38080:38080
environment:
- SERVE_PORT=38080
# if you have your own mysql, config it here, and disable the 'mysql' config blow
- MYSQL_URL=mysql # links will maintain /etc/hosts, just use 'container_name'
- MYSQL_PORT=3306
- MYSQL_USERNAME=root
- MYSQL_PASSWD=
- MYSQL_SCHEMA=rap2
# redis config
- REDIS_URL=redis
- REDIS_PORT=6379
# production / development
- NODE_ENV=production
###### 'sleep 30 && node scripts/init' will drop the tables
###### RUN ONLY ONCE THEN REMOVE 'sleep 30 && node scripts/init'
command: /bin/sh -c 'node dispatch.js'
# init the databases
# command: sleep 30 && node scripts/init && node dispatch.js
# without init
# command: node dispatch.js
depends_on:
- redis
- mysql
redis:
image: redis:4
# disable this if you have your own mysql
mysql:
image: mysql:5.7
# expose 33306 to client (navicat)
#ports:
# - 33306:3306
volumes:
# change './docker/mysql/volume' to your own path
# WARNING: without this line, your data will be lost.
- "./docker/mysql/volume:/var/lib/mysql"
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;' --innodb-flush-log-at-trx-commit=0
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MYSQL_DATABASE: "rap2"
MYSQL_USER: "root"
MYSQL_PASSWORD: ""
================================================
FILE: docs/Schedule.md
================================================
## 2017.05.15~05.26 计划
1. RAP1 数据迁移测试
2. 发布线上服务,自测在项目中的体验
3. 编写公开 API 的文档(注释)
4. 其他参与的同学参照 v2.1 需求和约定 http://gitlab.alibaba-inc.com/thx/rap2-delos/blob/master/docs/Design.md,和仓库中的 TODO 任务注释
5. 陪产假期间在家办公,业务支持直接电话我
> TODO 消消乐
## 2017.06.12~06.16 部署
**线上 RAP1 数据迁移基本完成,正在测试迁移数据。**
### 服务端 rap2-delos
1. 正式发布部署和迁移
### 前端 rap2-dolores
1. 正式发布部署和测试
## 2017.06.05~06.09 部署
**v2.1 开发完成,部署基本完成。**
### 服务端 rap2-delos
1. 正式发布部署
1. 接入 KeyCenter
2. 优化 分离拥有的仓库和加入的仓库
3. 优化 分离拥有的组织和加入的组织
4. 新增 fetch 拦截插件
### 前端 rap2-dolores
1. 正式发布部署
1. 接入 线上统一登录
2. 接入 线上域名
2. 协同 服务端的『分离拥有的仓库和加入的仓库』
3. 协同 服务端的『分离拥有的组织和加入的组织』
4. 新增 支持查看其他用户的仓库
5. 其他零散代码、视觉、交互优化
1. 视觉 增加拥有者 icon
2. 交互 首页 新用户显示引导文案『新建仓库』
## 2017.05.31~06.02
### 服务端 rap2-delos **2.1 开发完成**
1. 修复 数字属性、布尔属性、数组属性的解析和初始化
2. 修复 当前端发送 JSONP 请求,并且响应内容是字符串时,字符串响应再次执行 JSON.stringify(),导致响应内容格式错误
3. 增加 JSONSchema 接口 /app/mock/schema/:interfaceId
4. 完善 RAP1 迁移脚本
1. 修正 类型 array<number|string|object|boolean> => Array
2. 修正 模拟值 @mock=function(){} => Function
3. 修正 顺序值 $order => Array|+1: []
5. 完善 仓库接口测试页面,支持动态仓库 id
6. 新增 支持虚拟属性 __root__
7. 正式发布部署(未完)
1. 调整 Dockerfile 配置
2. 接入 VIPServer
3. 数据库上线
4. 接入 KeyCenter(未完)
### 前端 rap2-dolores
1. 增加 生成规则帮助链接
2. 增加 访问不存在仓库的编辑器时提示 404
3. 协同 后端的『修复 数字属性、布尔属性、数组属性的解析和初始化』
4. 增加 公开接口
5. 代码优化
1. 删除 遗留的无效注释
2. 删除 不再使用的 Fetching 组件
3. 增加 RModal 重定位截流
4. 完善 登陆时只使用 email 和 password,丢弃其他属性(非 BUS SSO 场景)
5. 删除 遗留的 corporation、product、grouping 代码
6. 完善 补全团队列表的 propTypes
7. 修复 不解析原始类型的初始值
8. 部署 暂时访问 daily 环境,上线后再恢复
6. 视觉优化
1. 增加 自动获得焦点:组织、仓库、模块、接口、属性、导入器、注册、登陆
2. 视觉 润色首页日志格式
3. 视觉 仓库列表和团队列表的最小高度为 10rem,增大没有找到匹配数据时的字号
4. 恢复 团队成员头像
5. 增加 协同仓库的帮助信息
6. 增加 组件 Popover 支持自定义 width
7. 视觉 组件 MembersInput 默认底部外边距 10px
8. 视觉 润色表单 input 的宽度
9. 视觉 移除 .rapfont,统一改用 react-icons
10. 视觉 润色接口编辑器
11. 新增 仓库编辑器初始加载时显示动画
7. 修复 属性类型 Number 并且初始值为 '' 时,被解析为随机字符串
8. 完善 删除团队、仓库、模块、接口时的确认提示
9. 新增 导入器支持格式化输入的 JSON
10. 修复 导入器重复调用 handleAddMemoryProperty() 丢失临时属性
## 2017.05.22~05.26
### 服务端 rap2-delos
1. 支持 迁移 RAP1 数据(开发和本地调试完成,待线上验证)
2. 完善 jQuery 插件、Mock 插件、插件文档 public/libs/README.md
3. 增加 检测和提示仓库中的重复接口
4. 修复 初始化新模块时创建了重复的示例接口
5. 支持 仓库协同(即 RAP1 的项目路由,用于指定与哪些项目共享 mock 数据)
6. 修复 creatorId 必须是当前登录用户,不需要前端传入
7. 修复 测试用例创建的临时仓库没有及时移除
8. 重构 IDB 结构设计
1. 清理 历史遗留表 user、repository、module、interface、property、organization、organization_members、logger、notification
2. 新建 仓库协同表 repositories_collaborators
3. 新建 账户通知表 notifications
4. 新增 字段 organizations.visibility,用于支持私有团队(待前端支持)
5. 新增 字段 repositories.visibility,用于支持私有仓库(待前端支持)
9. 调整 接口 /app/get 的位置,从 routes/mock.js 分散到 routes/account|organization|repository.js
10. 修复 当创建者或拥有者已经不存在时,仓库列表和组织列表的总记录数错误
11. 支持 转移团队 /organization/transfer(待前端支持)
12. 支持 转移仓库 /repository/transfer(待前端支持)
13. 优化 获取单个仓库完整数据的性能
14. 完善 示例接口初始化时填充更多的 Mock 规则示例
### 前端 rap2-dolores
1. 修复 /app/plugin/:repositories 接收到无效 repositoryId 时报错
2. 调整 导航栏,我的仓库=>仓库,团队仓库=>组织
3. 增加 仓库/全部仓库
4. 增加 检测和提示仓库中的重复接口
5. 修复 『我创建和加入的团队』不应该有分页
6. 协同 后端的『仓库协同』
7. 视觉 润色仓库编辑器
8. 修复 仓库编辑权限的判断逻辑
## 2017.05.15~05.19
### 服务端 rap2-delos
1. 修复 团队测试用例的用户 id 不存在
2. 修复 接口 /repository/joined 不排除自己拥有的项目
3. 完善 模拟数据接口 /app/mock/:repository/:method/:url
1. 支持响应多个仓库的数据
2. 支持不同的 http method
3. 完善相应的测试用例
4. 支持过滤重复仓库 id
5. 完善注释内容,增加关于直接通过 interface id 获取模板和数据的说明
6. 增加请求属性和响应属性的 Mock 模板
4. 重构 IDB 结构设计,为迁移 RAP1 数据做准备
1. 清理历史遗留表 corporation、product、grouping、project、page、action
2. 清理历史遗留字段 property.template、property.page、property.project、module.project
3. 利用 Sequelize 重构所有表之间的关联关系(代码更精简)
4. 修改所有外键的命名,风格统一为 modelId(减少歧义)
5. 调整所有涉及的模型、路由、测试用例和初始数据
5. 清理 历史 API 示例 HTML(已经全部改为测试用例)
6. 新增 前端插件适配 jQuery、Mock(待测试)
7. 完善 SQL 日志格式
8. 完善 生成数据模板时的异常日志格式
9. 完善 测试用例:用户、组织、仓库
### 前端 rap2-dolores
1. 新增 测试器 Tester(未完)
2. 引入 react-icons,因为 iconfont 的质量参差不齐,在 React 中使用不方便
3. 完善 仓库列表、组织列表的视觉:废弃 table 布局,类型文案改为 <select>
4. 修复 组件 Popover 定位错误
5. 修复 进入仓库编辑器时先展示上一次编辑过的仓库,直到当前仓库的数据返回后才会更新
6. 修复 仓库数据返回之前,会展示不完整的静态内容
7. 引入 RCodeMirror,作为接口属性导入工具的编辑器
8. 协同 后端的『重构 IDB 结构设计』
9. 优化 没有搜索到匹配的组织、仓库时的提示
## 2017.05.12
### 服务端 rap2-delos
1. 新增 Sequelize 持久化时字段值校验
2. 重构 初始数据,测试用户从 100000000 开始
3. 新增 按 name 或 id 搜索用户、仓库、组织
4. 修复 插件中的接口字段
5. 新增 属性增加生成规则字段 rule
6. 新增 所有响应 JSON 增加字段 update_date,前端可以当作版本号进行版本检测
7. 新增 用户日志 /account/logger(未完)
8. 修复 更新仓库、组织时意外变更 owner 和 creator
9. 修复 仓库的字段 members 没有成员时返回 [null] 导致前端渲染报错
10. 完善 格式化 /app/mock/:repository/:url 的响应,方便前端阅读和调试
### 前端 rap2-dolores
1. 重构 整站交互和样式,导航固定为首页、我的仓库、团队仓库、状态
2. 重构 组织相关功能,代码和逻辑更清爽条理
3. 修正 全局计数器 fetching
4. 新增 成员输入组件 MembersInput
5. 重构 仓库相关功能,代码和逻辑更清爽条理
6. 放弃 样式组件 .panel,改为 .card
7. 新增 用户日志(未完)
8. 修复 组件 build 后初始 state 为 null
9. 新增 表单验证(未完)
10. 引入 NProgress
11. 完善 表单输入一律禁用 spellcheck
12. 完善 整站开屏动画
13. 完善 预览 JSON 模板和 JSON 数据
14. 新增 RModal 组件,用于替换繁琐的 DialogController
15. 修复 接口测试时仓库 id 错误
## 2017.04.28
### 服务端 rap2-delos
1. 重构整个项目:目录结构、依赖包、代码规范 standardjs、pre-commit 检测
2. 增加测试用例 Account、Organization、Worksapce、Mock
3. v2.1 需求和约定 http://gitlab.alibaba-inc.com/thx/rap2-delos/blob/master/docs/Design.md
4. 安全修复:属性为死循环函数导致服务宕掉
### 前端 rap2-dolores
1. 接入集团统一登陆
2. 增加代码规范 standardjs、pre-commit 检测
3. 支持编辑模块、页面、接口
================================================
FILE: package.json
================================================
{
"name": "rap2-delos",
"version": "2.9.0",
"repository": {
"url": "https://github.com/thx/rap2-delos"
},
"description": "",
"main": "dist/dispatch.js",
"scripts": {
"build": "rimraf -rf dist/ && tsc",
"test": "cross-env NODE_ENV=development cross-env TEST_MODE=true nyc mocha test/**/*.js",
"check": "echo \"Checking...\" && tsc && npm run lint",
"dev": "cross-env NODE_ENV=development nodemon --watch scripts --watch dist dist/scripts/dev.js",
"create-db": "cross-env NODE_ENV=development node dist/scripts/initSchema",
"start": "cross-env NODE_ENV=production pm2 start dist/dispatch.js --name=rap-server-delos",
"lint": "echo \"TSLint checking...\" && tslint -c tslint.json --fix 'src/**/*.ts' 'src/**/*.tsx'",
"start:redis": "pm2 start redis-server --name redis-server",
"clean": "pm2 delete all",
"build-docker": "docker build --rm -f \"Dockerfile\" -t rapteam/rap2-delos:latest ."
},
"mocha": {
"timeout": 8000,
"slow": 200,
"exit": true,
"allowUncaught": true
},
"author": "bosn, nuysoft",
"license": "ISC",
"dependencies": {
"@types/treeify": "^1.0.0",
"chalk": "^3.0.0",
"cross-env": "^6.0.3",
"graceful": "^1.0.2",
"is-md5": "^0.0.2",
"js-beautify": "^1.10.3",
"json5": "^2.1.1",
"kcors": "^2.2.2",
"koa": "^2.11.0",
"koa-body": "^4.1.1",
"koa-generic-session": "^2.0.4",
"koa-logger": "^3.2.1",
"koa-redis": "^4.0.1",
"koa-router": "^8.0.8",
"koa-send": "^5.0.0",
"koa-static": "^5.0.0",
"lodash": "^4.17.15",
"mariadb": "^2.2.0",
"md5": "^2.2.1",
"mockjs": "1.1.0",
"moment": "^2.24.0",
"mysql": "^2.18.1",
"mysql2": "^2.1.0",
"nanoid": "^2.1.11",
"node-fetch": "^2.6.0",
"node-print": "0.0.4",
"node-schedule": "^1.3.2",
"nodemailer": "^6.4.10",
"notevil": "^1.3.3",
"path-to-regexp": "^3.1.0",
"redis": "^3.0.2",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"request-promise": "^4.2.5",
"sequelize": "^5.22.3",
"sequelize-typescript": "^1.1.0",
"svg-captcha": "^1.4.0",
"treeify": "^1.1.0",
"underscore": "^1.9.1",
"urllib": "^2.34.1",
"vm2": "^3.8.4"
},
"devDependencies": {
"@types/chai": "^4.2.10",
"@types/json5": "^0.0.30",
"@types/kcors": "^2.2.3",
"@types/koa": "^2.11.2",
"@types/koa-generic-session": "^1.0.3",
"@types/koa-logger": "^3.1.1",
"@types/koa-redis": "^4.0.0",
"@types/koa-router": "^7.4.0",
"@types/koa-static": "^4.0.1",
"@types/lodash": "^4.14.149",
"@types/mocha": "^8.0.0",
"@types/mockjs": "^1.0.2",
"@types/nanoid": "^2.1.0",
"@types/node": "^13.7.7",
"@types/node-schedule": "^1.3.0",
"@types/nodemailer": "^6.4.0",
"@types/redis": "^2.8.16",
"@types/request": "^2.48.4",
"@types/request-promise": "^4.1.45",
"@types/sequelize": "^4.28.8",
"@types/underscore": "^1.9.4",
"babel-eslint": "^10.1.0",
"chai": "^4.2.0",
"mocha": "^8.0.1",
"nodemon": "^2.0.2",
"npm-run-all": "^4.1.5",
"nyc": "^15.0.0",
"pre-commit": "^1.2.2",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.16",
"standard": "^14.3.1",
"supertest": "^4.0.2",
"tslint": "^6.0.0",
"typescript": "^3.8.3"
},
"pre-commit": [
"check"
]
}
================================================
FILE: public/404.json
================================================
{ "isOk": false, "errMsg": "找不到接口,请检查您的配置。 Can not find your API, please check your configurations."}
================================================
FILE: public/500.json
================================================
{ "isOk": false, "errMsg": "服务器内部错误,请联系管理员。 Server side internal error occurred, please contact the administrator of this system."}
================================================
FILE: public/error.html
================================================
<!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>
Opps, 发生了错误.... 请联系管理员....
</body>
</html>
================================================
FILE: public/index.html
================================================
<!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>
RAP2后端服务已启动,请从前端服务(rap2-dolores)访问。
RAP2 back-end server is started, please visit via front-end service (rap2-dolores).
</body>
</html>
================================================
FILE: public/libs/README.md
================================================
## RAP2 提供两种拦截方式
1. 引入 `libs/jquery.rap.js`
用复写的 `jQuery.ajax` 拦截与 RAP2 接口设置匹配的 ajax 请求,然后转发至 RAP2。
2. 引入 `libs/mock.rap.js`
由 Mock 拦截与 RAP2 接口设置匹配的 ajax 请求,然后直接返回响应数据,不会转发至 RAP2。
================================================
FILE: public/libs/fetch.rap.js
================================================
;(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)
================================================
FILE: public/libs/jquery.rap.js
================================================
;(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)
================================================
FILE: public/libs/mock.rap.js
================================================
;(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)
================================================
FILE: public/test/index.html
================================================
<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>
================================================
FILE: public/test/test.plugin.fetch.html
================================================
<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>
================================================
FILE: public/test/test.plugin.jquery.html
================================================
<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>
================================================
FILE: public/test/test.plugin.mock.html
================================================
<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>
================================================
FILE: public/test/test.request.js
================================================
/* 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)
})
})
}
}
================================================
FILE: release/v2.6_20180705.md
================================================
# V2.6 更新说明 2018-7-5
* 数据库字段变更请执行 `datbase/v2.6***`
* 因字段变更,需要清空redis缓存
1. 运行redis-cli
2. 执行 `flushall` 命令清空缓存
================================================
FILE: src/config/config.dev.ts
================================================
import { IConfigOptions } from '../types'
const config: IConfigOptions = {
version: 'v2.9.0',
serve: {
port: (process.env.SERVE_PORT && parseInt(process.env.SERVE_PORT)) || 8080,
path: '',
},
keys: ["some secret hurr"],
session: {
key: 'rap2:sess',
},
db: {
// dialect: 'mysql',
// host: process.env.MYSQL_URL || 'tddl.daily2.alibaba.net',
dialect: "mysql",
host: process.env.MYSQL_URL ?? "localhost",
port: (process.env.MYSQL_PORT && parseInt(process.env.MYSQL_PORT)) || 3306,
username: process.env.MYSQL_USERNAME ?? "root",
password: process.env.MYSQL_PASSWD ?? "",
database: process.env.MYSQL_SCHEMA ?? "RAP2_DELOS_APP",
pool: {
max: 10,
min: 0,
idle: 10000,
},
logging: false,
dialectOptions: {
connectTimeout: 20000
}
},
redis: {},
mail: {
host: process.env.MAIL_HOST ?? "smtp.aliyun.com",
port: process.env.MAIL_PORT ?? 465,
secure: process.env.MAIL_SECURE ?? true,
auth: {
user: process.env.MAIL_USER ?? "rap2org@service.alibaba.com",
pass: process.env.MAIL_PASS ?? ""
}
},
mailSender: process.env.MAIL_SENDER ?? 'rap2org@service.alibaba.com',
}
export default config
================================================
FILE: src/config/config.local.ts
================================================
import { IConfigOptions } from "../types"
let config: IConfigOptions = {
version: '2.9.0',
serve: {
port: (process.env.SERVE_PORT && parseInt(process.env.SERVE_PORT)) || 8080,
path: '',
},
keys: ["some secret hurr"],
session: {
key: 'rap2:sess',
},
db: {
dialect: "mysql",
host: process.env.MYSQL_URL || "localhost",
port: (process.env.MYSQL_PORT && parseInt(process.env.MYSQL_PORT)) || 3306,
username: process.env.MYSQL_USERNAME || "root",
password: process.env.MYSQL_PASSWD || "",
database: process.env.MYSQL_SCHEMA || "RAP2_DELOS_APP_LOCAL",
pool: {
max: 5,
min: 0,
idle: 10000,
},
logging: false
},
redis: {},
mail: {
host: process.env.MAIL_HOST ?? "smtp-mail.outlook.com",
port: process.env.MAIL_PORT ?? 587,
secure: process.env.MAIL_SECURE ?? false,
auth: {
user: process.env.MAIL_USER ?? "",
pass: process.env.MAIL_PASS ?? ""
}
},
mailSender: process.env.MAIL_SENDER ?? ""
}
export default config
================================================
FILE: src/config/config.prod.ts
================================================
import { IConfigOptions } from '../types'
// 先从环境变量取配置
let config: IConfigOptions = {
version: '2.9.0',
serve: {
port: (process.env.SERVE_PORT && parseInt(process.env.SERVE_PORT)) || 8080,
path: '',
},
keys: ["some secret hurr"],
session: {
key: 'rap2:sess',
},
db: {
dialect: 'mysql',
host: process.env.MYSQL_URL || 'localhost',
port: (process.env.MYSQL_PORT && parseInt(process.env.MYSQL_PORT)) || 3306,
username: process.env.MYSQL_USERNAME || 'root',
password: process.env.MYSQL_PASSWD || '',
database: process.env.MYSQL_SCHEMA || 'rap',
pool: {
max: 80,
min: 0,
idle: 20000,
acquire: 20000,
},
logging: false,
},
redis: {
host: process.env.REDIS_URL || 'localhost',
port: (process.env.REDIS_PORT && parseInt(process.env.REDIS_PORT)) || 6379,
password: process.env.REDIS_PWD || undefined,
},
mail: {
host: process.env.MAIL_HOST ?? 'smtp.aliyun.com',
port: process.env.MAIL_PORT ?? 465,
secure: process.env.MAIL_SECURE ?? true,
auth: {
user: process.env.MAIL_USER ?? 'rap2org@service.alibaba.com',
pass: process.env.MAIL_PASS ?? '',
},
},
mailSender: process.env.MAIL_SENDER ?? "rap2org@service.alibaba.com"
}
export default config
================================================
FILE: src/config/index.ts
================================================
import { IConfigOptions } from "../types"
// local or development or production
let configObj: IConfigOptions =
(process.env.NODE_ENV === 'local' && require('./config.local')).default ||
(process.env.NODE_ENV === 'development' && require('./config.dev')).default ||
require('./config.prod').default
export default configObj
================================================
FILE: src/dispatch.ts
================================================
import * as cluster from 'cluster'
import * as path from '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(() => {
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()
})
}
================================================
FILE: src/helpers/dedent.ts
================================================
/**
* copy from https://github.com/MartinKolarik/dedent-js/blob/master/src/index.ts
* 解决多行字符串缩进时的空格问题
*/
export default function dedent(
templateStrings: TemplateStringsArray | string,
...values: any[]
) {
let matches = []
let strings =
typeof templateStrings === 'string'
? [templateStrings]
: templateStrings.slice()
// 1. Remove trailing whitespace.
strings[strings.length - 1] = strings[strings.length - 1].replace(
/\r?\n([\t ]*)$/,
''
)
// 2. Find all line breaks to determine the highest common indentation level.
for (let i = 0; i < strings.length; i++) {
let match
if ((match = strings[i].match(/\n[\t ]+/g))) {
matches.push(...match)
}
}
// 3. Remove the common indentation from all strings.
if (matches.length) {
let size = Math.min(...matches.map(value => value.length - 1))
let pattern = new RegExp(`\n[\t ]{${size}}`, 'g')
for (let i = 0; i < strings.length; i++) {
strings[i] = strings[i].replace(pattern, '\n')
}
}
// 4. Remove leading whitespace.
strings[0] = strings[0].replace(/^\r?\n/, '')
// 5. Perform interpolation.
let string = strings[0]
for (let i = 0; i < values.length; i++) {
string += values[i] + strings[i + 1]
}
return string
}
================================================
FILE: src/helpers/pandoc.ts
================================================
import { spawnSync } from 'child_process'
export default function pandoc(from: string, to: string, ...args: string[]) {
const option = ['-f', from, '-t', to].concat(args)
return function converter(input: any) {
let pandoc
input = Buffer.from(input)
try {
pandoc = spawnSync('pandoc', option, { input, timeout: 20000 })
} catch (err) {
console.error(err)
console.error(pandoc.stderr)
}
if (pandoc.stderr && pandoc.stderr.length) {
console.error(pandoc.output[2])
throw new Error(pandoc.output[2].toString())
}
return pandoc.stdout
}
}
================================================
FILE: src/models/bo/defaultVal.ts
================================================
import { Table, Column, Model, AutoIncrement, PrimaryKey, ForeignKey } from 'sequelize-typescript'
import { Repository } from '../../models'
@Table({ paranoid: true, freezeTableName: false, timestamps: true, tableName: 'default_val' })
export default class DefaultVal extends Model<DefaultVal> {
@PrimaryKey
@AutoIncrement
@Column
id: number
@Column
name: string
@Column
rule: string
@Column
value: string
@ForeignKey(() => Repository)
@Column
repositoryId: number
}
================================================
FILE: src/models/bo/historyLog.ts
================================================
import { Table, Column, Model, AutoIncrement, PrimaryKey, DataType, AllowNull, ForeignKey, BelongsTo } from 'sequelize-typescript'
import { ENTITY_TYPE } from '../../routes/utils/const'
import { User } from '../../models'
@Table({ paranoid: true, freezeTableName: false, timestamps: true, tableName: 'history_log' })
export default class HistoryLog extends Model<HistoryLog> {
@AllowNull(false)
@PrimaryKey
@AutoIncrement
@Column
id: number
/**
* ENTITY_TYPE.INTERFACE: 接口级日志
* ENTITY_TYPE.REPOSITORY: 接口被删除、模块被移除等超出接口级的日志
*/
@AllowNull(false)
@Column({ type: DataType.INTEGER }) // for extension, type to INT, code as enum
entityType: ENTITY_TYPE
@AllowNull(false)
@Column
entityId: number
/**
* 文本日志,支持Markdown,可能存在不同版本,以MarkDown输出即可
*/
@AllowNull(false)
@Column({ type: DataType.TEXT })
changeLog: string
/**
* 可空,当发现变更较大(删除、修改字段较多、或整个接口的删除时),记录对应实体的Model JSON用于恢复。
*/
@Column({ type: DataType.TEXT })
relatedJSONData: string
@AllowNull(false)
@ForeignKey(() => User)
@Column
userId: number
@BelongsTo(() => User, 'userId')
user: User
jsonDataIsNull?: boolean
}
export const LOG_SEPERATOR = '.|.'
export const LOG_SUB_SEPERATOR = '@|@'
================================================
FILE: src/models/bo/interface.ts
================================================
import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey, BeforeBulkDestroy, BeforeBulkCreate, BeforeBulkUpdate, BeforeCreate, BeforeUpdate, BeforeDestroy } from 'sequelize-typescript'
import { User, Module, Repository, Property } from '../'
import RedisService, { CACHE_KEY } from '../../service/redis'
import * as Sequelize from 'sequelize'
import { BODY_OPTION } from '../../routes/utils/const'
const Op = Sequelize.Op
enum methods { GET = 'GET', POST = 'POST', PUT = 'PUT', DELETE = 'DELETE' }
export enum MoveOp {
MOVE = 1,
COPY = 2
}
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Interface extends Model<Interface> {
/** hooks */
@BeforeCreate
@BeforeUpdate
@BeforeDestroy
static async deleteCache(instance: Interface) {
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, instance.repositoryId)
}
@BeforeBulkCreate
@BeforeBulkUpdate
@BeforeBulkDestroy
static async bulkDeleteCache(options: any) {
let id: number = options && options.attributes && options.attributes.id
if (!id) {
id = options.where && +options.where.id
}
if (options.where && options.where[Op.and]) {
const arr = options.where[Op.and]
if (arr && arr[1] && arr[1].id) {
id = arr[1].id
}
}
if ((id as any) instanceof Array) {
id = (id as any)[0]
}
if (id) {
const itf = await Interface.findByPk(id)
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, itf.repositoryId)
}
}
public static METHODS = methods
public request?: object
public response?: object
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column(DataType.STRING(256))
name: string
@AllowNull(false)
@Column(DataType.STRING(256))
url: string
@AllowNull(false)
@Column({ comment: 'API method' })
method: string
@Column({ type: DataType.STRING(255) })
bodyOption?: BODY_OPTION
@Column(DataType.TEXT)
description: string
@AllowNull(false)
@Default(1)
@Column(DataType.BIGINT())
priority: number
@Default(200)
@Column
status: number
@ForeignKey(() => User)
@Column
creatorId: number
@ForeignKey(() => User)
@Column
lockerId: number
@ForeignKey(() => Module)
@Column
moduleId: number
@ForeignKey(() => Repository)
@Column
repositoryId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => User, 'lockerId')
locker: User
@BelongsTo(() => Module, 'moduleId')
module: Module
@BelongsTo(() => Repository, 'repositoryId')
repository: Repository
@HasMany(() => Property, 'interfaceId')
properties: Property[]
}
================================================
FILE: src/models/bo/logger.ts
================================================
import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, BelongsTo, ForeignKey } from 'sequelize-typescript'
import { User, Repository, Organization, Module, Interface } from '../'
enum types {
CREATE = 'create', UPDATE = 'update', DELETE = 'delete',
LOCK = 'lock', UNLOCK = 'unlock', JOIN = 'join', EXIT = 'exit',
}
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Logger extends Model<Logger> {
public static TYPES = types
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column({
type: DataType.ENUM(types.CREATE, types.UPDATE, types.DELETE, types.LOCK, types.UNLOCK, types.JOIN, types.EXIT),
comment: 'operation type',
})
type: string
@ForeignKey(() => User)
@Column
creatorId: number
@AllowNull(false)
@ForeignKey(() => User)
@Column
userId: number
@ForeignKey(() => Organization)
@Column
organizationId: number
@ForeignKey(() => Repository)
@Column
repositoryId: number
@ForeignKey(() => Module)
@Column
moduleId: number
@ForeignKey(() => Interface)
@Column
interfaceId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => User, 'userId')
user: User
@BelongsTo(() => Repository, 'repositoryId')
repository: Repository
@BelongsTo(() => Organization, 'organizationId')
organization: Organization
@BelongsTo(() => Module, 'moduleId')
module: Module
@BelongsTo(() => Interface, 'interfaceId')
interface: Interface
}
================================================
FILE: src/models/bo/module.ts
================================================
import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey, BeforeCreate, BeforeUpdate, BeforeDestroy, BeforeBulkCreate, BeforeBulkDestroy, BeforeBulkUpdate } from 'sequelize-typescript'
import { User, Repository, Interface } from '../'
import RedisService, { CACHE_KEY } from '../../service/redis'
import * as Sequelize from 'sequelize'
const Op = Sequelize.Op
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Module extends Model<Module> {
/** hooks */
@BeforeCreate
@BeforeUpdate
@BeforeDestroy
static async deleteCache(instance: Module) {
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, instance.repositoryId)
}
@BeforeBulkCreate
@BeforeBulkUpdate
@BeforeBulkDestroy
static async bulkDeleteCache(options: any) {
let id: number = options && options.attributes && options.attributes.id
if (!id) {
id = options.where && +options.where.id
}
if (options.where && options.where[Op.and]) {
const arr = options.where[Op.and]
if (arr && arr[1] && arr[1].id) {
id = arr[1].id
}
}
if ((id as any) instanceof Array) {
id = (id as any)[0]
}
if (id) {
const mod = await Module.findByPk(id)
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, mod.repositoryId)
}
}
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column(DataType.STRING(256))
name: string
@AllowNull(false)
@Column(DataType.TEXT)
description: string
@AllowNull(false)
@Default(1)
@Column(DataType.BIGINT())
priority: number
@ForeignKey(() => User)
@Column
creatorId: number
@ForeignKey(() => Repository)
@Column
repositoryId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => Repository, 'repositoryId')
repository: Repository
@HasMany(() => Interface, 'moduleId')
interfaces: Interface[]
}
================================================
FILE: src/models/bo/notification.ts
================================================
import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, Default } from 'sequelize-typescript'
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Notification extends Model<Notification> {
@AutoIncrement
@PrimaryKey
@Column
id: number
@Column({ comment: 'sender' })
fromId: number
@AllowNull(false)
@Column({ comment: 'receiver' })
toId: number
@AllowNull(false)
@Column({ comment: 'msg type' })
type: string
@Column(DataType.STRING(128))
param1: string
@Column(DataType.STRING(128))
param2: string
@Column(DataType.STRING(128))
param3: string
@AllowNull(false)
@Default(false)
@Column
readed: boolean
}
================================================
FILE: src/models/bo/organization.ts
================================================
import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMany, ForeignKey, AfterCreate } from 'sequelize-typescript'
import { User, Repository, OrganizationsMembers, Logger } from '../'
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Organization extends Model<Organization> {
@AfterCreate
static async createLog(instance: Organization) {
await Logger.create({
userId: instance.creatorId,
type: 'create',
organizationId: instance.id
})
}
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column(DataType.STRING(256))
name: string
@Column(DataType.TEXT)
description: string
@Column(DataType.STRING(256))
logo: string
@AllowNull(false)
@Default(true)
@Column({ comment: 'true:public, false:private' })
visibility: boolean
@ForeignKey(() => User)
@Column
creatorId: number
@ForeignKey(() => User)
@Column
ownerId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => User, 'ownerId')
owner: User
@BelongsToMany(() => User, () => OrganizationsMembers)
members: User[]
@HasMany(() => OrganizationsMembers)
organizationMembersList: OrganizationsMembers[]
@HasMany(() => Repository, 'organizationId')
repositories: Repository[]
}
================================================
FILE: src/models/bo/organizationsMembers.ts
================================================
import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'
import { User, Organization } from '../'
@Table({ freezeTableName: true, timestamps: true, tableName: 'organizations_members' })
export default class OrganizationsMembers extends Model<OrganizationsMembers> {
@ForeignKey(() => User)
@PrimaryKey
@Column
userId: number
@ForeignKey(() => Organization)
@PrimaryKey
@Column
organizationId: number
}
================================================
FILE: src/models/bo/property.ts
================================================
import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'sequelize-typescript'
import { User, Interface, Module, Repository } from '../'
export enum SCOPES { REQUEST = 'request', RESPONSE = 'response', SCRIPT = 'script' }
export enum TYPES { STRING = 'String', NUMBER = 'Number', BOOLEAN = 'Boolean', OBJECT = 'Object', ARRAY = 'Array', FUNCTION = 'Function', REGEXP = 'RegExp', Null = 'Null' }
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Property extends Model<Property> {
public static TYPES = TYPES
public static SCOPES = SCOPES
@AutoIncrement
@PrimaryKey
@Column
id: number
static attributes: any
@AllowNull(false)
@Default(SCOPES.RESPONSE)
@Column({
type: DataType.ENUM(SCOPES.REQUEST, SCOPES.RESPONSE),
comment: 'property owner',
})
scope: string
@AllowNull(false)
@Column({
type: DataType.ENUM(TYPES.STRING, TYPES.NUMBER, TYPES.BOOLEAN, TYPES.OBJECT, TYPES.ARRAY, TYPES.FUNCTION, TYPES.REGEXP, TYPES.Null),
comment: 'property type',
})
/** Data Type */
type: string
@AllowNull(false)
@Default(2)
@Column({ type: DataType.INTEGER }) // for better extension
/** request params type (position) */
pos: POS_TYPE
@AllowNull(false)
@Column(DataType.STRING(256))
name: string
@Column({ type: DataType.STRING(128), comment: 'property generation rules' })
rule: string
@Column({ type: DataType.TEXT, comment: 'value of this property' })
value: string
@Column(DataType.TEXT)
description: string
@AllowNull(false)
@Default(-1)
@Column({ comment: 'parent property ID' })
parentId: number
@AllowNull(false)
@Default(1)
@Column(DataType.BIGINT())
priority: number
@ForeignKey(() => Interface)
@Column
interfaceId: number
@ForeignKey(() => User)
@Column
creatorId: number
@ForeignKey(() => Module)
@Column
moduleId: number
@ForeignKey(() => Repository)
@Column
repositoryId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => Interface, 'interfaceId')
interface: Interface
@BelongsTo(() => Module, 'moduleId')
module: Module
@BelongsTo(() => Repository, 'repositoryId')
repository: Repository
@Column
/** 是否为必填选项 */
required: boolean
}
/**
* 参数类型
*/
export enum POS_TYPE {
QUERY = 2,
HEADER = 1,
BODY = 3,
PRE_REQUEST_SCRIPT = 4,
TEST = 5,
}
================================================
FILE: src/models/bo/repositoriesCollaborators.ts
================================================
import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'
import { Repository } from '../'
@Table({ freezeTableName: true, timestamps: true, tableName: 'repositories_collaborators' })
export default class RepositoriesCollaborators extends Model<RepositoriesCollaborators> {
@ForeignKey(() => Repository)
@PrimaryKey
@Column
repositoryId: number
@ForeignKey(() => Repository)
@PrimaryKey
@Column
collaboratorId: number
}
================================================
FILE: src/models/bo/repositoriesMembers.ts
================================================
import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'
import { Repository, User } from '../'
@Table({ freezeTableName: true, timestamps: false, tableName: 'repositories_members' })
export default class RepositoriesMembers extends Model<RepositoriesMembers> {
@ForeignKey(() => User)
@PrimaryKey
@Column
userId: number
@ForeignKey(() => Repository)
@PrimaryKey
@Column
repositoryId: number
}
================================================
FILE: src/models/bo/repository.ts
================================================
import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMany, ForeignKey, BeforeUpdate, BeforeCreate, BeforeDestroy, BeforeBulkCreate, BeforeBulkUpdate, BeforeBulkDestroy } from 'sequelize-typescript'
import { User, Organization, Module, Interface, RepositoriesCollaborators } from '../'
import RedisService, { CACHE_KEY } from '../../service/redis'
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class Repository extends Model<Repository> {
/** hooks */
@BeforeCreate
@BeforeUpdate
@BeforeDestroy
static async cleanCache(instance: Repository) {
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, instance.id)
}
@BeforeBulkCreate
@BeforeBulkUpdate
@BeforeBulkDestroy
static async bulkDeleteCache(options: any) {
const id = options && options.attributes && options.attributes.id
if (id) {
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, id)
}
}
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column(DataType.STRING(256))
name: string
@Column(DataType.TEXT)
description: string
@Column(DataType.STRING(256))
logo: string
@Column(DataType.STRING(32))
token: string
@AllowNull(false)
@Default(true)
@Column({ comment: 'true:public, false:private' })
visibility: boolean
@ForeignKey(() => User)
@Column
ownerId: number
@ForeignKey(() => Organization)
@Column
organizationId: number
@ForeignKey(() => User)
@Column
creatorId: number
@ForeignKey(() => User)
@Column
lockerId: number
@BelongsTo(() => User, 'creatorId')
creator: User
@BelongsTo(() => User, 'ownerId')
owner: User
@BelongsTo(() => Organization, 'organizationId')
organization: Organization
@BelongsTo(() => User, 'lockerId')
locker: User
@BelongsToMany(() => User, 'repositories_members', 'repositoryId', 'userId')
members: User[]
@HasMany(() => Module, 'repositoryId')
modules: Module[]
@HasMany(() => Module, 'repositoryId')
interfaces: Interface[]
@BelongsToMany(() => Repository, () => RepositoriesCollaborators, 'repositoryId', 'collaboratorId')
collaborators: Repository[]
@BelongsToMany(() => Repository, () => RepositoriesCollaborators, 'collaboratorId')
repositories: Repository[]
}
================================================
FILE: src/models/bo/user.ts
================================================
import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Unique, BelongsToMany } from 'sequelize-typescript'
import { Organization, Repository, OrganizationsMembers, RepositoriesMembers } from '../'
@Table({ paranoid: true, freezeTableName: false, timestamps: true })
export default class User extends Model<User> {
@AutoIncrement
@PrimaryKey
@Column
id: number
@AllowNull(false)
@Column(DataType.STRING(32))
fullname: string
@Column(DataType.STRING(32))
password: string
@AllowNull(false)
@Unique
@Column(DataType.STRING(128))
email: string
@HasMany(() => Organization, 'ownerId')
ownedOrganizations: Organization[]
@BelongsToMany(() => Organization, () => OrganizationsMembers)
joinedOrganizations: Organization[]
@HasMany(() => Repository, 'ownerId')
ownedRepositories: Repository[]
@BelongsToMany(() => Repository, () => RepositoriesMembers)
joinedRepositories: Repository[]
}
================================================
FILE: src/models/helper.ts
================================================
export let Helper: any = {
include: [],
exclude: {
generalities: ['createdAt', 'updatedAt', 'deletedAt', 'reserve'],
},
}
================================================
FILE: src/models/index.ts
================================================
export { default as QueryInclude } from './util/queryInclude'
export { default as Interface } from './bo/interface'
export { default as Logger } from './bo/logger'
export { default as Module } from './bo/module'
export { default as Notification } from './bo/notification'
export { default as Organization } from './bo/organization'
export { default as Property } from './bo/property'
export { default as Repository } from './bo/repository'
export { default as User } from './bo/user'
export { default as OrganizationsMembers } from './bo/organizationsMembers'
export { default as RepositoriesCollaborators } from './bo/repositoriesCollaborators'
export { default as RepositoriesMembers } from './bo/repositoriesMembers'
export { default as DefaultVal } from './bo/defaultVal'
export { default as HistoryLog } from './bo/historyLog'
================================================
FILE: src/models/sequelize.ts
================================================
import { Sequelize } from 'sequelize-typescript'
import config from '../config'
import MigrateService from '../service/migrate'
const chalk = require('chalk')
const now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
const logging = process.env.NODE_ENV === 'development'
? (sql: string) => {
sql = sql.replace('Executing (default): ', '')
console.log(`${chalk.bold('SQL')} ${now()} ${chalk.gray(sql)}`)
}
: console.log
const sequelize = new Sequelize({
database: config.db.database,
dialect: config.db.dialect,
username: config.db.username,
password: config.db.password,
host: config.db.host,
port: config.db.port,
pool: config.db.pool,
logging: config.db.logging ? logging : false,
dialectOptions: config.db.dialectOptions,
})
sequelize.addModels([__dirname + '/bo'])
sequelize.authenticate()
.then((/* err */) => {
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('----------------------------------------')
MigrateService.checkAndFix()
})
// sequelize.sync()
.catch(err => {
console.log('Unable to connect to the database:', err)
})
export default sequelize
================================================
FILE: src/models/util/helper.ts
================================================
declare interface IHelper {
include: string[],
exclude: {
generalities: string[],
}
}
export let Helper: IHelper = {
include: [],
exclude: {
generalities: ['createdAt', 'updatedAt', 'deletedAt', 'reserve'],
},
}
================================================
FILE: src/models/util/queryInclude.ts
================================================
// TODO 2.2 如何缓存重复查询?https://github.com/rfink/sequelize-redis-cache
import { Helper } from './helper'
import User from '../bo/user'
import Repository from '../bo/repository'
import Module from '../bo/module'
import Organization from '../bo/organization'
import Interface from '../bo/interface'
import Property from '../bo/property'
import { IncludeOptions } from 'sequelize'
declare interface IQueryInclude {
[key: string]: IncludeOptions
}
const QueryInclude: IQueryInclude = {
User: {
model: User,
as: 'user',
attributes: { exclude: ['password', ...Helper.exclude.generalities] },
required: true,
},
UserForSearch: {
model: User,
as: 'user',
attributes: { include: ['id', 'fullname'] },
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,
include: [
{
model: Interface,
as: 'interfaces',
attributes: { exclude: [] },
required: false,
include: [
{
model: User,
as: 'locker',
attributes: { exclude: ['password', ...Helper.exclude.generalities] },
required: false,
},
{
model: Property,
as: 'properties',
attributes: { exclude: [] },
required: false,
},
],
},
],
},
RepositoryHierarchyExcludeProperty: {
model: Module,
as: 'modules',
attributes: { exclude: [] },
required: false,
include: [
{
model: Interface,
as: 'interfaces',
attributes: { exclude: [] },
required: false,
include: [
{
model: User,
as: 'locker',
attributes: { exclude: ['password', ...Helper.exclude.generalities] },
required: false,
},
],
},
],
},
Properties: {
model: Property,
as: 'properties',
attributes: { exclude: [] },
required: false,
},
}
export default QueryInclude
================================================
FILE: src/routes/account.ts
================================================
import * as svgCaptcha from 'svg-captcha'
import { User, Notification, Logger, Organization, Repository } from '../models'
import router from './router'
import { Model } from 'sequelize-typescript'
import Pagination from './utils/pagination'
import { QueryInclude } from '../models'
import { Op } from 'sequelize'
import MailService from '../service/mail'
import * as md5 from 'md5'
import { isLoggedIn } from './base'
import { AccessUtils } from './utils/access'
import { COMMON_ERROR_RES } from './utils/const'
import * as moment from 'moment'
import RedisService, { CACHE_KEY, DEFAULT_CACHE_VAL } from '../service/redis'
router.get('/app/get', async (ctx, next) => {
let data: any = {}
let query = ctx.query
let hooks: any = {
user: User,
}
for (let name in hooks) {
if (!query[name]) continue
data[name] = await hooks[name].findByPk(query[name], {
attributes: { exclude: [] }
})
}
ctx.body = {
data: Object.assign({}, ctx.body && ctx.body.data, data),
}
return next()
})
router.get('/account/count', async (ctx) => {
ctx.body = {
data: await User.count(),
}
})
router.get('/account/list', isLoggedIn, async (ctx) => {
// if (!AccessUtils.isAdmin(ctx.session.id)) {
// ctx.body = COMMON_ERROR_RES.ACCESS_DENY
// return
// }
let where = {}
let { name } = ctx.query
if (name) {
Object.assign(where, {
[Op.or]: [
{ fullname: { [Op.like]: `%${name}%` } },
{ email: name },
],
})
}
let options = { where }
let total = await User.count(options)
let limit = Math.min(+ctx.query.limit ?? 10, 100)
let pagination = new Pagination(total, ctx.query.cursor || 1, limit)
ctx.body = {
data: await User.findAll({
...options, ...{
attributes: ['id', 'fullname', 'email'],
offset: pagination.start,
limit: pagination.limit,
order: [['id', 'DESC']],
}
}),
pagination: pagination
}
})
router.get('/account/info', async (ctx) => {
ctx.body = {
data: ctx.session.id ? await User.findByPk(ctx.session.id, {
attributes: QueryInclude.User.attributes
}) : undefined
}
})
router.post('/account/login', async (ctx) => {
let { email, password, captcha } = ctx.request.body
let result, errMsg
if (process.env.TEST_MODE !== 'true' &&
(!captcha || !ctx.session.captcha || captcha.trim().toLowerCase() !== ctx.session.captcha.toLowerCase())) {
errMsg = '错误的验证码'
} else {
result = await User.findOne({
attributes: QueryInclude.User.attributes,
where: { email, password: md5(md5(password)) },
})
if (result) {
ctx.session.id = result.id
ctx.session.fullname = result.fullname
ctx.session.email = result.email
let app: any = ctx.app
app.counter.users[result.fullname] = true
} else {
errMsg = '账号或密码错误'
}
}
ctx.body = {
data: result ? result : { errMsg },
}
})
router.get('/captcha_data', ctx => {
ctx.body = {
data: JSON.stringify(ctx.session)
}
})
router.get('/account/logout', async (ctx) => {
let app: any = ctx.app
delete 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) => {
let { fullname, email, password } = ctx.request.body
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: md5(md5(password)) })
if (result) {
ctx.session.id = result.id
ctx.session.fullname = result.fullname
ctx.session.email = result.email
let app: any = ctx.app
app.counter.users[result.fullname] = true
}
ctx.body = {
data: {
id: result.id,
fullname: result.fullname,
email: result.email,
},
}
})
router.post('/account/update', async (ctx) => {
const { password } = ctx.request.body
let errMsg = ''
let isOk = false
if (!ctx.session || !ctx.session.id) {
errMsg = '登陆超时'
} else if (password.length < 6) {
errMsg = '密码长度过短'
} else {
const user = await User.findByPk(ctx.session.id)
user.password = md5(md5(password))
await user.save()
isOk = true
}
ctx.body = {
data: {
isOk,
errMsg
}
}
})
router.get('/account/remove', isLoggedIn, async (ctx) => {
if (!AccessUtils.isAdmin(ctx.session.id)) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
if (process.env.TEST_MODE === 'true') {
ctx.body = {
data: await User.destroy({
where: { id: ctx.query.id },
}),
}
} else {
ctx.body = {
data: {
isOk: false,
errMsg: 'access forbidden',
},
}
}
})
// TODO 2.3 账户设置
router.get('/account/setting', async (ctx) => {
ctx.body = {
data: {},
}
})
router.post('/account/setting', async (ctx) => {
ctx.body = {
data: {},
}
})
router.post('/account/fetchUserSettings', isLoggedIn, async (ctx) => {
const keys: CACHE_KEY[] = ctx.request.body.keys
if (!keys || !keys.length) {
ctx.body = {
isOk: false,
errMsg: 'error'
}
return
}
const data: { [key: string]: string } = {}
for (const key of keys) {
data[key] = await RedisService.getCache(key, ctx.session.id) || DEFAULT_CACHE_VAL[key]
}
ctx.body = {
isOk: true,
data,
}
})
router.post('/account/updateUserSetting/:key', isLoggedIn, async (ctx) => {
const key: CACHE_KEY = ctx.params.key as CACHE_KEY
const value: string = ctx.request.body.value
await RedisService.setCache(key, value, ctx.session.id, 10 * 365 * 24 * 60 * 60)
ctx.body = {
isOk: true,
}
})
// TODO 2.3 账户通知
let NOTIFICATION_EXCLUDE_ATTRIBUTES: any = []
router.get('/account/notification/list', async (ctx) => {
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) => {
ctx.body = {
data: [],
}
})
router.post('/account/notification/unreaded', async (ctx) => {
ctx.body = {
data: 0,
}
})
router.post('/account/notification/read', async (ctx) => {
ctx.body = {
data: 0,
}
})
// TODO 2.3 账户日志
router.get('/account/logger', async (ctx) => {
if (!ctx.session.id) {
ctx.body = {
data: {
isOk: false,
errMsg: 'not login'
}
}
return
}
let auth = await User.findByPk(ctx.session.id)
let repositories: Model<Repository>[] = [...(<Model<Repository>[]>await auth.$get('ownedRepositories')), ...(<Model<Repository>[]>await auth.$get('joinedRepositories'))]
let organizations: Model<Organization>[] = [...(<Model<Organization>[]>await auth.$get('ownedOrganizations')), ...(<Model<Organization>[]>await auth.$get('joinedOrganizations'))]
let where: any = {
[Op.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,
} as any)
ctx.body = {
data: logs,
pagination,
}
})
router.get('/captcha', async (ctx) => {
const captcha = svgCaptcha.create()
ctx.session.captcha = captcha.text
ctx.set('Content-Type', 'image/svg+xml')
ctx.body = captcha.data
})
router.get('/worker', async (ctx) => {
ctx.body = process.env.NODE_APP_INSTANCE || 'NOT FOUND'
})
router.post('/account/reset', async (ctx) => {
const email = ctx.request.body.email
const password = ctx.request.body.password
if (password && ctx.session.resetCode && password === ctx.session.resetCode + '') {
const newPassword = String(Math.floor(Math.random() * 99999999))
const user = await User.findOne({ where: { email } })
if (!user) {
ctx.body = {
data: {
isOk: false,
errMsg: '您的邮箱没被注册过。',
}
}
return
}
user.password = md5(md5(newPassword))
await user.save()
ctx.body = {
data: {
isOk: true,
data: newPassword,
}
}
} else {
const resetCode = ctx.session.resetCode = Math.floor(Math.random() * 999999)
MailService.send(email, 'RAP重置账户验证码', `您的验证码为:${resetCode}`)
ctx.body = {
data: {
isOk: true,
}
}
}
})
router.post('/account/findpwd', async (ctx) => {
let { email, captcha } = ctx.request.body
let user, errMsg
if (process.env.TEST_MODE !== 'true' &&
(!captcha || !ctx.session.captcha || captcha.trim().toLowerCase() !== ctx.session.captcha.toLowerCase())) {
errMsg = '错误的验证码'
} else {
user = await User.findOne({
attributes: QueryInclude.User.attributes,
where: { email },
})
if (user) {
// 截取ID最后两位*日期字符串 作为返回链接的过期校验
let idstr = user.id.toString()
let timeCode = (parseInt(moment().add(60, 'minutes').format('YYMMDDHHmmss')) * parseInt(idstr.substr(idstr.length - 2))).toString()
let token = md5(user.email + user.id + timeCode + String(Math.floor(Math.random() * 99999999)))
await RedisService.setCache(CACHE_KEY.PWDRESETTOKEN_GET, token, user.id)
let link = `${ctx.headers.origin}/account/resetpwd?code=${timeCode}&email=${email}&token=${token}`
let content = MailService.mailFindpwdTemp.replace(/{=EMAIL=}/g, user.email).replace(/{=URL=}/g, link).replace(/{=NAME=}/g, user.fullname)
MailService.send(email, "RAP2:重新设置您的密码", content)
} else {
errMsg = '账号不存在'
}
}
ctx.body = {
data: !errMsg ? { isOk: true } : { isOk: false, errMsg }
}
})
router.post('/account/findpwd/reset', async (ctx) => {
let { code, email, captcha, token, password } = ctx.request.body
let user, errMsg
if (!code || !email || !captcha || !token || !password) {
errMsg = '参数错误'
}
else if (password.length < 6) {
errMsg = '密码长度过短'
}
else if (process.env.TEST_MODE !== 'true' &&
(!captcha || !ctx.session.captcha || captcha.trim().toLowerCase() !== ctx.session.captcha.toLowerCase())) {
errMsg = '错误的验证码'
} else {
user = await User.findOne({
attributes: QueryInclude.User.attributes,
where: { email },
})
if (!user) {
errMsg = '您的邮箱没被注册过,或用户已被锁定'
}
else {
const tokenCache = await RedisService.getCache(CACHE_KEY.PWDRESETTOKEN_GET, user.id)
if (!tokenCache || tokenCache !== token) {
errMsg = "参数错误"
}
else {
RedisService.delCache(CACHE_KEY.PWDRESETTOKEN_GET, user.id)
let idstr = user.id.toString()
let timespan = parseInt(code) / parseInt(idstr.substr(idstr.length - 2))
if (timespan < parseInt(moment().format('YYMMDDHHmmss'))) {
errMsg = "此链接已超时,请重新发送重置密码邮件"
}
else {
user.password = md5(md5(password))
await user.save()
}
}
}
}
ctx.body = {
data: !errMsg ? { isOk: true } : { isOk: false, errMsg }
}
})
router.post('/account/updateAccount', async ctx => {
try {
const { password, fullname } = ctx.request.body as { password: string, fullname: string }
if (!ctx.session?.id) {
throw new Error('需先登录才能操作')
}
const user = await User.findByPk(ctx.session.id)
if (password) {
user.password = md5(md5(password))
}
if (fullname) {
user.fullname = fullname
}
await user.save()
ctx.body = {
isOk: true
}
} catch (ex) {
ctx.body = {
isOk: false,
errMsg: ex.message,
}
}
})
================================================
FILE: src/routes/analytics.ts
================================================
import router from './router'
import Repository from "../models/bo/repository"
import Logger from "../models/bo/logger"
import User from "../models/bo/user"
const moment = require('moment')
const Sequelize = require('sequelize')
const SELECT = { type: Sequelize.QueryTypes.SELECT }
import sequelize from '../models/sequelize'
import { isLoggedIn } from './base'
const YYYY_MM_DD = 'YYYY-MM-DD'
// 最近 30 天新建仓库数
router.get('/app/analytics/repositories/created', isLoggedIn, async (ctx) => {
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
${Repository.getTableName()}
WHERE
createdAt >= '${start}' AND createdAt <= '${end}'
GROUP BY label
ORDER BY label ASC;
`
let result: any = await sequelize.query(sql, SELECT)
result = result.map((item: any) => ({
label: moment(item.label).format(YYYY_MM_DD),
value: item.value,
}))
ctx.body = {
data: result,
}
})
// 最近 30 天活跃仓库数
router.get('/app/analytics/repositories/updated', isLoggedIn, async (ctx) => {
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
${Repository.getTableName()}
WHERE
updatedAt >= '${start}' AND updatedAt <= '${end}'
GROUP BY label
ORDER BY label ASC;
`
let result: any = await sequelize.query(sql, SELECT)
result = result.map((item: any) => ({
label: moment(item.label).format(YYYY_MM_DD),
value: item.value,
}))
ctx.body = {
data: result,
}
})
// 最近 30 天活跃用户
router.get('/app/analytics/users/activation', isLoggedIn, async (ctx) => {
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.fullname AS fullname,
COUNT(*) AS value
FROM
${Logger.getTableName()} loggers
LEFT JOIN
${User.getTableName()} 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', isLoggedIn, async (ctx) => {
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
${Logger.getTableName()} loggers
LEFT JOIN
${Repository.getTableName()} 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
================================================
FILE: src/routes/base.ts
================================================
import * as _ from 'lodash'
import { ParameterizedContext } from 'koa'
const inTestMode = process.env.TEST_MODE === 'true'
export async function isLoggedIn(ctx: ParameterizedContext<any, any>, next: () => Promise<any>) {
if (!inTestMode && (!ctx.session || !ctx.session.id)) {
ctx.body = {
isOk: false,
errMsg: 'need login',
}
} else {
await next()
}
}
================================================
FILE: src/routes/counter.ts
================================================
import router from './router'
import config from '../config'
router.get('/app/counter', async(ctx) => {
let app: any = ctx.app
ctx.body = {
data: {
version: config.version,
users: Object.keys(app.counter.users).length,
mock: app.counter.mock,
},
}
})
================================================
FILE: src/routes/export.ts
================================================
import router from './router'
import { COMMON_ERROR_RES } from './utils/const'
import PostmanService from '../service/export/postman'
import MarkdownService from '../service/export/markdown'
// import PDFService from '../service/export/pdf'
import * as moment from 'moment'
import DocxService from '../service/export/docx'
import { AccessUtils, ACCESS_TYPE } from './utils/access'
import { Repository } from '../models/'
router.get('/export/postman', async ctx => {
const repoId = +ctx.query.id
if (
!(await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_GET,
ctx.session.id,
repoId
))
) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
if (!(repoId > 0)) {
ctx.data = COMMON_ERROR_RES.ERROR_PARAMS
}
const repository = await Repository.findByPk(repoId)
ctx.body = await PostmanService.export(repoId)
ctx.set(
'Content-Disposition',
`attachment; filename="RAP-${encodeURI(
repository.name
)}-${repoId}-${encodeURI('POSTMAN')}-${moment().format('YYYYMMDDHHmmss')}.json"`
)
ctx.set(
'Content-type',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
})
router.get('/export/markdown', async ctx => {
const repoId = +ctx.query.id
if (
!(await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_GET,
ctx.session.id,
repoId
))
) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
if (!(repoId > 0)) {
ctx.data = COMMON_ERROR_RES.ERROR_PARAMS
}
ctx.body = await MarkdownService.export(repoId, ctx.query.origin as string)
})
router.get('/export/docx', async ctx => {
const repoId = +ctx.query.id
if (
!(await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_GET,
ctx.session.id,
repoId
))
) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
if (!(repoId > 0)) {
ctx.data = COMMON_ERROR_RES.ERROR_PARAMS
}
const repository = await Repository.findByPk(repoId)
ctx.body = await DocxService.export(repoId, ctx.query.origin as string)
ctx.set(
'Content-Disposition',
`attachment; filename="RAP-${encodeURI(
repository.name
)}-${repoId}-${encodeURI('接口文档')}-${moment().format('YYYYMMDDHHmmss')}.docx"`
)
ctx.set(
'Content-type',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
})
// router.get('/export/pdf', async ctx => {
// const repoId = +ctx.query.id
// if (
// !(await AccessUtils.canUserAccess(
// ACCESS_TYPE.REPOSITORY,
// ctx.session.id,
// repoId
// ))
// ) {
// ctx.body = COMMON_ERROR_RES.ACCESS_DENY
// return
// }
// if (!(repoId > 0)) {
// ctx.data = COMMON_ERROR_RES.ERROR_PARAMS
// }
// const repository = await Repository.findByPk(repoId)
// ctx.body = await PDFService.export(repoId, ctx.query.origin)
// ctx.set(
// 'Content-Disposition',
// `attachment; filename="RAP-${encodeURI(
// repository.name
// )}-${repoId}-${encodeURI('接口文档')}.pdf"`
// )
// ctx.set(
// 'Content-type',
// 'application/pdf'
// )
// })
================================================
FILE: src/routes/index.ts
================================================
import router from './router'
require('./counter')
require('./account')
require('./organization')
require('./repository')
require('./mock')
require('./analytics')
require('./export')
export default router
================================================
FILE: src/routes/migration.ts
================================================
// import router from './router'
// import migration from '../scripts/migration/migration'
// // TODO 2.3 迁移期间采用单 worker 机制,迁移后恢复
// const TASKS: { [key: number]: number } = {}
// const doit = async (repositoryId: number) => {
// if (repositoryId in TASKS) return
// let stage = await migration.stage(repositoryId)
// if (stage === 4) {
// TASKS[repositoryId] = 100
// return
// }
// await migration.repository(repositoryId)
// await migration.lock(repositoryId)
// TASKS[repositoryId] = 0
// for (let i = 0; i < 100; i++) {
// const percent = await new Promise<number>((resolve) => {
// setTimeout(() => {
// resolve(i + 1)
// }, Math.random() * 500)
// })
// TASKS[repositoryId] = percent
// }
// }
// router.post('/app/migrate', async (ctx) => {
// let { repositoryId } = ctx.request.body
// if (!repositoryId) return
// doit(repositoryId)
// ctx.type = 'json'
// ctx.body = {
// percent: TASKS[repositoryId] || 0
// }
// })
// router.get('/app/migrate/progress/:repositoryId', async (ctx) => {
// let { repositoryId } = ctx.params
// let stage = await migration.stage(repositoryId)
// let percent = stage === 4 ? 100 : TASKS[repositoryId]
// ctx.body = {
// percent
// }
// })
================================================
FILE: src/routes/mock.ts
================================================
import router from './router'
import { Repository, Interface, Property } from '../models'
import { QueryInclude } from '../models'
import Tree from './utils/tree'
import { MockService } from '../service/mock'
const attributes: any = { exclude: [] }
const pt = require('node-print').pt
const beautify = require('js-beautify').js_beautify
// 检测是否存在重复接口,会在返回的插件 JS 中提示。同时也会在编辑器中提示。
const parseDuplicatedInterfaces = (repository: Repository) => {
let counter: any = {}
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: any, host: any, repository: Repository) => {
// DONE 2.3 protocol 错误,应该是 https
let duplicated = parseDuplicatedInterfaces(repository)
let editor = `${protocol}://rap2.taobao.org/repository/editor?id=${repository.id}` // [TODO] replaced by cur domain
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: Interface) =>
`{ 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) => {
let repositoryIds = new Set<number>(ctx.params.repositories.split(',').map((item: string) => +item).filter((item: any) => item)) // _.uniq() => Set
let result = []
for (let id of repositoryIds) {
let repository = await Repository.findByPk(id, {
attributes: { exclude: [] },
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker,
QueryInclude.Members,
QueryInclude.Organization,
QueryInclude.Collaborators,
],
} as any)
if (!repository) continue
if (repository.collaborators) {
repository.collaborators.map(item => {
repositoryIds.add(item.id)
})
}
repository.interfaces = await Interface.findAll<Interface>({
attributes: { exclude: [] },
where: {
repositoryId: repository.id,
},
include: [
QueryInclude.Properties,
],
} as any)
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; charset=utf-8'
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/:repositoryId(\\d+)/:url(.+)', async (ctx) => {
await MockService.mock(ctx, { forceVerify: true })
})
router.all('/app/mock-noverify/:repositoryId(\\d+)/:url(.+)', async ctx => {
await MockService.mock(ctx, { forceVerify: false })
})
// DONE 2.2 支持获取请求参数的模板、数据、Schema
router.get('/app/mock/template/:interfaceId', async (ctx) => {
let app: any = ctx.app
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.all('/app/mock/data/:interfaceId', async (ctx) => {
let app: any = ctx.app
app.counter.mock++
let { interfaceId } = ctx.params
let { scope = 'response' } = ctx.query
let properties: any = await Property.findAll({
attributes,
where: { interfaceId, scope },
})
properties = properties.map((item: any) => item.toJSON())
// pt(properties)
// DONE 2.2 支持引用请求参数
let requestProperties: any = await Property.findAll({
attributes,
where: { interfaceId, scope: 'request' },
})
requestProperties = requestProperties.map((item: any) => item.toJSON())
let requestData = Tree.ArrayToTreeToTemplateToData(requestProperties)
Object.assign(requestData, ctx.query)
let data = Tree.ArrayToTreeToTemplateToData(properties, requestData)
ctx.type = 'json'
if (data._root_) {
data = data._root_
}
ctx.body = JSON.stringify(data, undefined, 2)
})
router.get('/app/mock/schema/:interfaceId', async (ctx) => {
let app: any = ctx.app
app.counter.mock++
let { interfaceId } = ctx.params
let { scope = 'response' } = ctx.query
let properties: any = await Property.findAll({
attributes,
where: { interfaceId, scope },
})
pt(properties.map((item: any) => item.toJSON()))
properties = properties.map((item: any) => item.toJSON())
let schema = Tree.ArrayToTreeToTemplateToJSONSchema(properties)
ctx.type = 'json'
ctx.body = Tree.stringifyWithFunctonAndRegExp(schema)
})
router.get('/app/mock/tree/:interfaceId', async (ctx) => {
let app: any = ctx.app
app.counter.mock++
let { interfaceId } = ctx.params
let { scope = 'response' } = ctx.query
let properties: any = await Property.findAll({
attributes,
where: { interfaceId, scope },
})
pt(properties.map((item: any) => item.toJSON()))
properties = properties.map((item: any) => item.toJSON())
let tree = Tree.ArrayToTree(properties)
ctx.type = 'json'
ctx.body = Tree.stringifyWithFunctonAndRegExp(tree)
})
================================================
FILE: src/routes/organization.ts
================================================
import router from './router'
import { Organization, User, Logger, Repository, Module, Interface, Property } from '../models'
import { QueryInclude } from '../models'
import * as _ from 'lodash'
import Pagination from './utils/pagination'
import OrganizationService from '../service/organization'
import { Op, FindOptions } from 'sequelize'
import { isLoggedIn } from './base'
import { AccessUtils, ACCESS_TYPE } from './utils/access'
import { COMMON_ERROR_RES } from './utils/const'
router.get('/app/get', async (ctx, next) => {
let data: any = {}
let query = ctx.query
let hooks: any = {
organization: Organization,
}
for (let name in hooks) {
if (!query[name]) continue
data[name] = await hooks[name].findByPk(query[name], {
attributes: { exclude: [] }
})
}
ctx.body = {
data: Object.assign({}, ctx.body && ctx.body.data, data),
}
return next()
})
router.get('/organization/count', async (ctx) => {
ctx.body = {
data: await Organization.count(),
}
})
router.get('/organization/list', async (ctx) => {
const curUserId = ctx.session.id
const { name } = ctx.query
const total = await OrganizationService.getAllOrganizationIdListNum(curUserId)
const pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100)
const organizationIds = await OrganizationService.getAllOrganizationIdList(curUserId, pagination, name as string)
const options: FindOptions = {
where: {
id: {
[Op.in]: organizationIds,
},
},
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Members
],
order: [['updatedAt', 'desc']]
}
const organizations = await Organization.findAll(options)
ctx.body = {
data: organizations,
pagination,
}
})
router.get('/organization/owned', isLoggedIn, async (ctx) => {
let where = {}
let { name } = ctx.query
if (name) {
Object.assign(where, {
[Op.or]: [
{ name: { [Op.like]: `%${name}%` } },
{ id: name } // name => id
]
})
}
let auth = await User.findByPk(ctx.session.id)
let options: any = {
where,
attributes: { exclude: [] },
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
order: [['updatedAt', 'DESC']],
}
let owned = await auth.$get('ownedOrganizations', options)
ctx.body = {
data: owned,
pagination: undefined,
}
})
router.get('/organization/joined', isLoggedIn, async (ctx) => {
let where = {}
let { name } = ctx.query
if (name) {
Object.assign(where, {
[Op.or]: [
{ name: { [Op.like]: `%${name}%` } },
{ id: name } // name => id
]
})
}
let auth = await User.findByPk(ctx.session.id)
let options: object = {
where,
attributes: { exclude: [] },
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
order: [['updatedAt', 'DESC']],
}
let joined = await auth.$get('joinedOrganizations', options)
// await auth.getOwnedOrganizations()
// await auth.getJoinedOrganizations()
ctx.body = {
data: joined,
pagination: undefined,
}
})
router.get('/organization/get', async (ctx) => {
const organizationId = +ctx.query.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_GET, ctx.session.id, organizationId)) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
const organization = await Organization.findByPk(+ctx.query.id, {
attributes: { exclude: [] },
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
} as any)
ctx.body = {
data: organization,
}
})
router.post('/organization/create', isLoggedIn, async (ctx) => {
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.$set('members', members)
}
let filled = await Organization.findByPk(created.id, {
attributes: { exclude: [] },
include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members],
} as any)
ctx.body = {
data: filled,
}
})
router.post('/organization/update', isLoggedIn, async (ctx, next) => {
let body = Object.assign({}, ctx.request.body)
const organizationId = +body.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
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.findByPk(body.id)
let members = await User.findAll({ where: { id: body.memberIds } })
ctx.prevAssociations = await reloaded.$get('members')
await reloaded.$set('members', members)
ctx.nextAssociations = await reloaded.$get('members')
}
ctx.body = {
data: updated[0],
}
return next()
}, async (ctx) => {
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: any) => item.id)
let nextIds = ctx.nextAssociations.map((item: any) => 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', isLoggedIn, async (ctx) => {
let { id, ownerId } = ctx.request.body
const organizationId = +id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
let body = { ownerId }
let result = await Organization.update(body, { where: { id } })
ctx.body = {
data: result[0],
}
})
router.get('/organization/remove', isLoggedIn, async (ctx, next) => {
let { id } = ctx.query
const organizationId = +id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
ctx.body = COMMON_ERROR_RES.ACCESS_DENY
return
}
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) => {
if (ctx.body.data === 0) return
let { id } = ctx.query
await Logger.create({
userId: ctx.session.id,
type: 'delete',
organizationId: id,
})
})
================================================
FILE: src/routes/repository.ts
================================================
// TODO 2.1 大数据测试,含有大量模块、接口、属性的仓库
import router from './router'
import * as _ from 'underscore'
import Pagination from './utils/pagination'
import { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger, DefaultVal } from '../models'
import Tree from './utils/tree'
import { AccessUtils, ACCESS_TYPE } from './utils/access'
import * as Consts from './utils/const'
import RedisService, { CACHE_KEY } from '../service/redis'
import RepositoryService from '../service/repository'
import MigrateService from '../service/migrate'
import OrganizationService from '../service/organization'
import { Op } from 'sequelize'
import { isLoggedIn } from './base'
import { initRepository, initModule } from './utils/helper'
import nanoid = require('nanoid')
import { LOG_SEPERATOR, LOG_SUB_SEPERATOR } from '../models/bo/historyLog'
import { ENTITY_TYPE } from './utils/const'
import { IPager } from '../types'
import * as JSON5 from 'json5'
router.get('/app/get', async (ctx, next) => {
let data: any = {}
let query = ctx.query
let hooks: any = {
repository: Repository,
module: Module,
interface: Interface,
property: Property,
user: User,
}
for (let name in hooks) {
if (!query[name]) continue
data[name] = await hooks[name].findByPk(query[name])
}
ctx.body = {
data: Object.assign({}, ctx.body && ctx.body.data, data),
}
return next()
})
router.get('/repository/count', async (ctx) => {
ctx.body = {
data: await Repository.count(),
}
})
router.get('/repository/list', async (ctx) => {
let where = {}
let { name, user, organization } = ctx.query
if (+organization > 0) {
const access = await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_GET, ctx.session.id, +organization)
if (access === false) {
ctx.body = {
isOk: false,
errMsg: Consts.COMMON_MSGS.ACCESS_DENY
}
return
}
}
// tslint:disable-next-line:no-null-keyword
if (user) Object.assign(where, { ownerId: user, organizationId: null })
if (organization) Object.assign(where, { organizationId: organization })
if (name) {
Object.assign(where, {
[Op.or]: [
{ name: { [Op.like]: `%${name}%` } },
{ id: name } // name => id
]
})
}
let total = await Repository.count({
where,
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker
]
})
let limit = Math.min(+ctx.query.limit ?? 10, 100)
let pagination = new Pagination(total, ctx.query.cursor || 1, limit)
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']]
})
let repoData = await Promise.all(repositories.map(async (repo) => {
const canUserEdit = await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_SET,
ctx.session.id,
repo.id,
)
return {
...repo.toJSON(),
canUserEdit
}
}))
ctx.body = {
isOk: true,
data: repoData,
pagination: pagination,
}
})
router.get('/repository/owned', isLoggedIn, async (ctx) => {
let where = {}
let { name } = ctx.query
if (name) {
Object.assign(where, {
[Op.or]: [
{ name: { [Op.like]: `%${name}%` } },
{ id: name } // name => id
]
})
}
let auth: User = await User.findByPk(ctx.query.user || ctx.session.id)
let repositories = await auth.$get('ownedRepositories', {
where,
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker,
QueryInclude.Members,
QueryInclude.Organization,
QueryInclude.Collaborators,
],
order: [['updatedAt', 'DESC']]
})
let repoData = repositories.map(repo => {
return {
...repo.toJSON(),
canUserEdit: true
}
})
ctx.body = {
data: repoData,
pagination: undefined,
}
})
router.get('/repository/joined', isLoggedIn, async (ctx) => {
let where: any = {}
let { name } = ctx.query
if (name) {
Object.assign(where, {
[Op.or]: [
{ name: { [Op.like]: `%${name}%` } },
{ id: name } // name => id
]
})
}
let auth = await User.findByPk(ctx.query.user || ctx.session.id)
let repositories = await auth.$get('joinedRepositories', {
where,
attributes: { exclude: [] },
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker,
QueryInclude.Members,
QueryInclude.Organization,
QueryInclude.Collaborators,
],
order: [['updatedAt', 'DESC']]
})
let repoData = repositories.map(repo => {
return {
...repo.toJSON(),
canUserEdit: true,
}
})
ctx.body = {
data: repoData,
pagination: undefined
}
})
router.get('/repository/get', async (ctx) => {
const access = await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_GET,
ctx.session.id,
+ctx.query.id,
ctx.query.token as string
)
if (access === false) {
ctx.body = {
isOk: false,
errMsg: Consts.COMMON_MSGS.ACCESS_DENY
}
return
}
const excludeProperty = ctx.query.excludeProperty || false
const canUserEdit = await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_SET,
ctx.session.id,
+ctx.query.id,
ctx.query.token as string,
)
let repository: Partial<Repository> & {
canUserEdit: boolean
}
// 分开查询减少查询时间
let [repositoryOmitModules, repositoryModules] = await Promise.all([
Repository.findByPk(+ctx.query.id, {
attributes: { exclude: [] },
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker,
QueryInclude.Members,
QueryInclude.Organization,
QueryInclude.Collaborators,
],
}),
Repository.findByPk(+ctx.query.id, {
attributes: { exclude: [] },
include: [
excludeProperty
? QueryInclude.RepositoryHierarchyExcludeProperty
: QueryInclude.RepositoryHierarchy,
],
order: [
[{ model: Module, as: 'modules' }, 'priority', 'asc'],
[
{ model: Module, as: 'modules' },
{ model: Interface, as: 'interfaces' },
'priority',
'asc',
],
],
}),
])
repository = {
...repositoryOmitModules.toJSON(),
...repositoryModules.toJSON(),
canUserEdit
}
ctx.body = {
data: repository,
}
})
router.post('/repository/create', isLoggedIn, async (ctx, next) => {
let creatorId = ctx.session.id
let body = Object.assign({}, ctx.request.body, {
creatorId,
ownerId: creatorId,
token: nanoid(32)
})
let created = await Repository.create(body)
if (body.memberIds) {
let members = await User.findAll({ where: { id: body.memberIds } })
await created.$set('members', members)
}
if (body.collaboratorIds) {
let collaborators = await Repository.findAll({ where: { id: body.collaboratorIds } })
await created.$set('collaborators', collaborators)
}
await initRepository(created)
ctx.body = {
data: await Repository.findByPk(created.id, {
attributes: { exclude: [] },
include: [
QueryInclude.Creator,
QueryInclude.Owner,
QueryInclude.Locker,
QueryInclude.Members,
QueryInclude.Organization,
QueryInclude.RepositoryHierarchy,
QueryInclude.Collaborators,
],
} as any),
}
return next()
}, async (ctx) => {
await Logger.create({
userId: ctx.session.id,
type: 'create',
repositoryId: ctx.body.data.id,
})
})
router.post('/repository/update', isLoggedIn, async (ctx, next) => {
const body = Object.assign({}, ctx.request.body)
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, body.id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let repo = await Repository.findByPk(body.id)
// 更改团队需要校验是否有当前团队和目标团队的权限
if (body.organizationId != repo.organizationId) {
if (body.organizationId && !(await OrganizationService.canUserAccessOrganization(ctx.session.id, body.organizationId))) {
ctx.body = '没有当前团队的权限'
return
}
if (repo.organizationId && !(await OrganizationService.canUserAccessOrganization(ctx.session.id, repo.organizationId))) {
ctx.body = '没有目标团队的权限'
return
}
}
delete body.creatorId
let result = await Repository.update(body, { where: { id: body.id } })
if (body.memberIds) {
let reloaded = await Repository.findByPk(body.id, {
include: [{
model: User,
as: 'members',
}],
})
let members = await User.findAll({
where: {
id: {
[Op.in]: body.memberIds,
},
},
})
ctx.prevAssociations = reloaded.members
reloaded.$set('members', members)
await reloaded.save()
ctx.nextAssociations = reloaded.members
}
if (body.collaboratorIds) {
let reloaded = await Repository.findByPk(body.id)
let collaborators = await Repository.findAll({
where: {
id: {
[Op.in]: body.collaboratorIds,
},
},
})
reloaded.$set('collaborators', collaborators)
await reloaded.save()
}
ctx.body = {
data: result[0],
}
return next()
}, async (ctx) => {
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: any) => item.id)
let nextIds = ctx.nextAssociations.map((item: any) => 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', isLoggedIn, async (ctx) => {
let { id, ownerId, organizationId } = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, organizationId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let body: any = {}
if (ownerId) body.ownerId = ownerId // 转移给其他用户
if (organizationId) {
body.organizationId = organizationId // 转移给其他团队,同时转移给该团队拥有者
body.ownerId = (await Organization.findByPk(organizationId)).ownerId
}
let result = await Repository.update(body, { where: { id } })
ctx.body = {
data: result[0],
}
})
router.get('/repository/remove', isLoggedIn, async (ctx, next) => {
const id = +ctx.query.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
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) => {
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', isLoggedIn, async (ctx) => {
const id = +ctx.request.body.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let user = ctx.session.id
if (!user) {
ctx.body = { data: 0 }
return
}
let result = await Repository.update({ lockerId: user }, {
where: { id },
})
ctx.body = { data: result[0] }
})
router.post('/repository/unlock', async (ctx) => {
if (!ctx.session.id) {
ctx.body = { data: 0 }
return
}
let { id } = ctx.request.body
// tslint:disable-next-line:no-null-keyword
let result = await Repository.update({ lockerId: null }, {
where: { id },
})
ctx.body = { data: result[0] }
})
// 模块
router.get('/module/count', async (ctx) => {
ctx.body = {
data: await Module.count(),
}
})
router.get('/module/list', async (ctx) => {
let where: any = {}
let { repositoryId, name } = ctx.query
if (repositoryId) where.repositoryId = repositoryId
if (name) where.name = { [Op.like]: `%${name}%` }
ctx.body = {
data: await Module.findAll({
attributes: { exclude: [] },
where,
}),
}
})
router.get('/module/get', async (ctx) => {
ctx.body = {
data: await Module.findByPk(+ctx.query.id, {
attributes: { exclude: [] }
})
}
})
router.post('/module/create', isLoggedIn, async (ctx, next) => {
let creatorId = ctx.session.id
let body = Object.assign(ctx.request.body, { creatorId })
body.priority = Date.now()
let created = await Module.create(body)
await initModule(created)
ctx.body = {
data: await Module.findByPk(created.id)
}
return next()
}, async (ctx) => {
let mod = ctx.body.data
await Logger.create({
userId: ctx.session.id,
type: 'create',
repositoryId: mod.repositoryId,
moduleId: mod.id,
})
})
router.post('/module/update', isLoggedIn, async (ctx, next) => {
const { id, name, description } = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let mod = await Module.findByPk(id)
await mod.update({ name, description })
ctx.request.body.repositoryId = mod.repositoryId
ctx.body = {
data: {
id,
name,
description
},
}
return next()
}, async (ctx) => {
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.post('/module/move', isLoggedIn, async ctx => {
const { modId, op } = ctx.request.body
const repositoryId = ctx.request.body.repositoryId
if (!(await RepositoryService.canUserMoveModule(ctx.session.id, modId, repositoryId))) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
await RepositoryService.moveModule(op, modId, repositoryId)
ctx.body = {
data: {
isOk: true,
},
}
})
router.get('/module/remove', isLoggedIn, async (ctx, next) => {
let { id } = ctx.query
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.MODULE_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
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) => {
if (ctx.body.data === 0) return
const id = +ctx.query.id
let mod = await Module.findByPk(id, { paranoid: false })
await Logger.create({
userId: ctx.session.id,
type: 'delete',
repositoryId: mod.repositoryId,
moduleId: mod.id,
})
})
router.post('/module/sort', isLoggedIn, async (ctx) => {
let { ids } = ctx.request.body
let counter = 1
for (let index = 0; index < ids.length; index++) {
await Module.update({ priority: counter++ }, {
where: { id: ids[index] }
})
}
if (ids && ids.length) {
const mod = await Module.findByPk(ids[0])
await RedisService.delCache(CACHE_KEY.REPOSITORY_GET, mod.repositoryId)
}
ctx.body = {
data: ids.length,
}
})
router.get('/interface/count', async (ctx) => {
ctx.body = {
data: await Interface.count(),
}
})
router.get('/interface/list', async (ctx) => {
let where: any = {}
let { repositoryId, moduleId, name } = ctx.query
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_GET, ctx.session.id, +repositoryId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
if (repositoryId) where.repositoryId = repositoryId
if (moduleId) where.moduleId = moduleId
if (name) where.name = { [Op.like]: `%${name}%` }
ctx.body = {
data: await Interface.findAll({
attributes: { exclude: [] },
where,
}),
}
})
router.get('/repository/defaultVal/get/:id', async (ctx) => {
const repositoryId: number = +ctx.params.id
ctx.body = {
data: await DefaultVal.findAll({ where: { repositoryId } })
}
})
router.post('/repository/defaultVal/update/:id', async (ctx) => {
const repositoryId: number = +ctx.params.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, repositoryId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
const list = ctx.request.body.list.map(x => { const { id, ...y } = x; return y })
if (!(repositoryId > 0) || !list) {
ctx.body = Consts.COMMON_ERROR_RES.ERROR_PARAMS
return
}
await DefaultVal.destroy({
where: { repositoryId }
})
for (const item of list) {
await DefaultVal.create({
...item,
repositoryId,
})
}
ctx.body = {
isOk: true,
}
})
router.get('/interface/get', async (ctx) => {
const id = +ctx.query.id
if (id === undefined || !id) {
ctx.body = {
isOk: false,
errMsg: '请输入参数id'
}
return
}
let itf = await Interface.findByPk(id, {
include: [QueryInclude.Locker],
attributes: { exclude: [] },
})
if (!itf) {
ctx.body = {
isOk: false,
errMsg: `没有找到 id 为 ${id} 的接口`
}
return
}
if (
!(await AccessUtils.canUserAccess(
ACCESS_TYPE.REPOSITORY_GET,
ctx.session.id,
itf.repositoryId
))
) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
const itfJSON: { [k: string]: any } = itf.toJSON()
let properties: any[] = await Property.findAll({
attributes: { exclude: [] },
where: { interfaceId: itf.id },
})
properties = properties.map((item: any) => item.toJSON())
itfJSON['properties'] = properties
let scopes = ['request', 'response']
for (let i = 0; i < scopes.length; i++) {
let scopeProperties = properties
.filter(p => p.scope === scopes[i])
.map((item: any) => ({ ...item }))
itfJSON[scopes[i] + 'Properties'] = Tree.ArrayToTree(scopeProperties).children
}
ctx.type = 'json'
ctx.body = Tree.stringifyWithFunctonAndRegExp({ data: itfJSON })
})
router.post('/interface/create', isLoggedIn, async (ctx, next) => {
let creatorId = ctx.session.id
let body = Object.assign(ctx.request.body, { creatorId })
body.priority = Date.now()
let created = await Interface.create(body)
// await initInterface(created)
ctx.body = {
data: {
itf: await Interface.findByPk(created.id),
}
}
return next()
}, async (ctx) => {
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', isLoggedIn, async (ctx, next) => {
let summary = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +summary.id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
const itf = await Interface.findByPk(summary.id)
const itfChangeLog: string[] = []
itf.name !== summary.name && itfChangeLog.push(`接口名 \`${itf.name}\` => \`${summary.name}\``)
itf.url !== summary.url && itfChangeLog.push(`URL \`${itf.url || '空URL'}\` => \`${summary.url}\``)
itf.method !== summary.method && itfChangeLog.push(`METHOD \`${itf.method}\` => \`${summary.method}\``)
itfChangeLog.length && await RepositoryService.addHistoryLog({
entityId: itf.id,
entityType: Consts.ENTITY_TYPE.INTERFACE,
changeLog: `接口${itf.name}(${itf.url || '空URL'}) 变更${itfChangeLog.join(LOG_SEPERATOR)}`,
userId: ctx.session.id,
})
await Interface.update(summary, {
where: { id: summary.id }
})
ctx.body = {
data: {
itf: await Interface.findByPk(summary.id),
}
}
return next()
}, async (ctx) => {
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.post('/interface/move', isLoggedIn, async ctx => {
const { modId, itfId, op } = ctx.request.body
const itf = await Interface.findByPk(itfId)
const repositoryId = ctx.request.body.repositoryId || itf.repositoryId
if (!(await RepositoryService.canUserMoveInterface(ctx.session.id, itfId, repositoryId, modId))) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
await RepositoryService.moveInterface(op, itfId, repositoryId, modId)
ctx.body = {
data: {
isOk: true,
},
}
})
router.get('/interface/remove', async (ctx, next) => {
let id = +ctx.query.id
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
const itf = await Interface.findByPk(id)
const properties = await Property.findAll({ where: { interfaceId: id } })
await RepositoryService.addHistoryLog({
entityId: itf.repositoryId,
entityType: Consts.ENTITY_TYPE.REPOSITORY,
changeLog: `接口 ${itf.name} (${itf.url}) 被删除,数据已备份。`,
userId: ctx.session.id,
relatedJSONData: JSON.stringify({ "itf": itf, "properties": properties })
})
let result = await Interface.destroy({ where: { id } })
await Property.destroy({ where: { interfaceId: id } })
ctx.body = {
data: result,
}
return next()
}, async (ctx) => {
if (ctx.body.data === 0) return
const id = +ctx.query.id
let itf = await Interface.findByPk(id, { paranoid: false })
await Logger.create({
userId: ctx.session.id,
type: 'delete',
repositoryId: itf.repositoryId,
moduleId: itf.moduleId,
interfaceId: itf.id,
})
})
router.get('/__test__', async (ctx) => {
const itf = await Interface.findByPk(5331)
itf.name = itf.name + '+'
await itf.save()
ctx.body = {
data: itf.name
}
})
router.post('/interface/lock', async (ctx, next) => {
if (!ctx.session.id) {
ctx.body = Consts.COMMON_ERROR_RES.NOT_LOGIN
return
}
let { id } = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let itf = await Interface.findByPk(id, {
attributes: ['lockerId'],
include: [
QueryInclude.Locker,
]
})
if (itf.lockerId) { // DONE 2.3 BUG 接口可能被不同的人重复锁定。如果已经被锁定,则忽略。
ctx.body = {
data: itf.locker,
}
return
}
await Interface.update({ lockerId: ctx.session.id }, { where: { id } })
itf = await Interface.findByPk(id, {
attributes: ['lockerId'],
include: [
QueryInclude.Locker,
]
})
ctx.body = {
data: itf.locker,
}
return next()
})
router.post('/interface/unlock', async (ctx) => {
if (!ctx.session.id) {
ctx.body = Consts.COMMON_ERROR_RES.NOT_LOGIN
return
}
let { id } = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let itf = await Interface.findByPk(id, { attributes: ['lockerId'] })
if (itf.lockerId !== ctx.session.id) { // DONE 2.3 BUG 接口可能被其他人解锁。如果不是同一个用户,则忽略。
ctx.body = {
isOk: false,
errMsg: '您不是锁定该接口的用户,无法对其解除锁定状态。请刷新页面。',
}
return
}
await Interface.update({
// tslint:disable-next-line:no-null-keyword
lockerId: null,
}, {
where: { id }
})
ctx.body = {
data: {
isOk: true,
}
}
})
router.post('/interface/sort', async (ctx) => {
let { ids } = ctx.request.body
let counter = 1
for (let index = 0; index < ids.length; index++) {
await Interface.update({ priority: counter++ }, {
where: { id: ids[index] }
})
}
ctx.body = {
data: ids.length,
}
})
router.get('/property/count', async (ctx) => {
ctx.body = {
data: 0
}
})
router.get('/property/list', async (ctx) => {
let where: any = {}
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 = { [Op.like]: `%${name}%` }
ctx.body = {
data: await Property.findAll({ where }),
}
})
router.get('/property/get', async (ctx) => {
const id = +ctx.query.id
ctx.body = {
data: await Property.findByPk(id, {
attributes: { exclude: [] }
})
}
})
router.post('/property/create', isLoggedIn, async (ctx) => {
let creatorId = ctx.session.id
let body = Object.assign(ctx.request.body, { creatorId })
let created = await Property.create(body)
ctx.body = {
data: await Property.findByPk(created.id, {
attributes: { exclude: [] }
})
}
})
router.post('/property/update', isLoggedIn, async (ctx) => {
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.rawAttributes))
let affected = await Property.update(property, {
where: { id: property.id },
})
result += affected[0]
}
ctx.body = {
data: result,
}
})
router.post('/properties/update', isLoggedIn, async (ctx, next) => {
const itfId = +ctx.query.itf
let needBackup = false
let changeCount = 0
let { properties, summary } = ctx.request.body as { properties: Property[], summary: Interface }
properties = Array.isArray(properties) ? properties : [properties]
let itf = await Interface.findByPk(itfId)
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.INTERFACE_SET, ctx.session.id, itfId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
if (summary.bodyOption) {
itf.bodyOption = summary.bodyOption
await itf.save()
}
const itfPropertiesChangeLog: string[] = []
// 删除不在更新列表中的属性
// 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
)
*/
const pLog = (p: Property, title: string) => `\`${title}\`${p.scope === 'request' ? '请求' : '响应'}参数\`${p.name}\`${p.description ? '(' + p.description + ')' : ''}`
const existingProperties = properties.filter((item: any) => !item.memory)
const existingPropertyIds = existingProperties.map(x => x.id)
const originalProperties = await Property.findAll({ where: { interfaceId: itfId } })
const backupJSON = JSON.stringify({ "itf": itf, "properties": originalProperties })
const deletedProperties = originalProperties.filter(x => existingPropertyIds.indexOf(x.id) === -1)
const deletedPropertyLog: string[] = []
for (const deletedProperty of deletedProperties) {
deletedPropertyLog.push(pLog(deletedProperty, '删除了'))
}
changeCount += deletedProperties.length
deletedPropertyLog.length && itfPropertiesChangeLog.push(deletedPropertyLog.join(LOG_SUB_SEPERATOR))
let result = await Property.destroy({
where: {
id: { [Op.notIn]: existingProperties.map((item: any) => item.id) },
interfaceId: itfId
}
})
const updatedPropertyLog: string[] = []
// 更新已存在的属性
for (let item of existingProperties) {
const changed: string[] = []
const o = originalProperties.filter(x => x.id === item.id)[0]
if (o) {
if (o.name !== item.name) {
changed.push(`变量名${o.name} => ${item.name}`)
}
// mock rules 不记入日志
if (o.type !== item.type) {
changed.push(`类型${o.type} => ${item.type}`)
}
changed.length && updatedPropertyLog.push(`${pLog(item, '更新了')} ${changed.join(' ')}`)
changeCount += changed.length
}
let affected = await Property.update(item, {
where: { id: item.id },
})
result += affected[0]
}
updatedPropertyLog.length && itfPropertiesChangeLog.push(updatedPropertyLog.join(LOG_SUB_SEPERATOR))
// 插入新增加的属性
let newProperties = properties.filter((item: any) => item.memory)
let memoryIdsMap: any = {}
const addedPropertyLog: string[] = []
for (let item of newProperties) {
let created = await Property.create(Object.assign({}, item, {
id: undefined,
parentId: -1,
priority: item.priority || Date.now()
}))
addedPropertyLog.push(pLog(item, '新增了'))
memoryIdsMap[item.id] = created.id
item.id = created.id
result += 1
}
changeCount += newProperties.length
addedPropertyLog.length && itfPropertiesChangeLog.push(addedPropertyLog.join(LOG_SUB_SEPERATOR))
// 同步 parentId
for (let item of newProperties) {
let parentId = memoryIdsMap[item.parentId] || item.parentId
await Property.update({ parentId }, {
where: { id: item.id },
})
}
itf = await Interface.findByPk(itfId, {
include: (QueryInclude.RepositoryHierarchy as any).include[0].include,
})
if (changeCount >= 5) {
needBackup = true
}
if (itfPropertiesChangeLog.length) {
await RepositoryService.addHistoryLog({
entityId: itf.id,
entityType: Consts.ENTITY_TYPE.INTERFACE,
changeLog: `接口 ${itf.name}(${itf.url}) 参数变更: ${itfPropertiesChangeLog.join(LOG_SEPERATOR)}${needBackup ? ', 改动较大已备份数据。' : ''}`,
userId: ctx.session.id,
...needBackup ? { relatedJSONData: backupJSON } : {},
})
}
ctx.body = {
data: {
result,
properties: itf.properties,
}
}
return next()
}, async (ctx) => {
if (ctx.body.data === 0) return
let itf = await Interface.findByPk(ctx.query.itf as string, {
attributes: { exclude: [] }
})
await Logger.create({
userId: ctx.session.id,
type: 'update',
repositoryId: itf.repositoryId,
moduleId: itf.moduleId,
interfaceId: itf.id,
})
})
router.get('/property/remove', isLoggedIn, async (ctx) => {
let { id } = ctx.query
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.PROPERTY_SET, ctx.session.id, +id)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
ctx.body = {
data: await Property.destroy({
where: { id },
}),
}
})
router.post('/repository/import', isLoggedIn, async (ctx) => {
const { docUrl, orgId, version, projectData } = ctx.request.body
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.ORGANIZATION_SET, ctx.session.id, orgId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
let success = false
let message = ''
try {
if (+version === 3) {
await MigrateService.importRepoFromJSON(JSON5.parse(projectData).data, ctx.session.id, true, orgId)
success = true
} else {
success = await MigrateService.importRepoFromRAP1DocUrl(orgId, ctx.session.id, docUrl, +version, projectData)
}
} catch (ex) {
success = false
message = ex.message
}
ctx.body = {
isOk: success,
message: success ? '导入成功' : `导入失败:${message}`,
}
})
router.post('/repository/importswagger', isLoggedIn, async (ctx) => {
const { orgId, repositoryId, swagger, version = 1, mode = 'manual' } = ctx.request.body
// 权限判断
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, repositoryId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
const result = await MigrateService.importRepoFromSwaggerDocUrl(orgId, ctx.session.id, swagger, version, mode, repositoryId)
ctx.body = {
isOk: result.code,
message: result.code === 'success' ? '导入成功' : '导入失败',
repository: {
id: 1,
}
}
})
router.post('/repository/importRAP2Backup', isLoggedIn, async (ctx) => {
const { repositoryId, swagger, modId } = ctx.request.body
// 权限判断
if (!await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, repositoryId)) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
try {
await MigrateService.importInterfaceFromJSON(swagger, ctx.session.id, repositoryId, modId)
ctx.body = {
isOk: 'success',
message: '导入成功',
repository: {
id: 1,
}
}
} catch (ex) {
ctx.body = {
isOk: 'failure',
message: `导入失败: ${ex.message}`,
}
}
})
router.post('/repository/importJSON', isLoggedIn, async ctx => {
const { data } = ctx.request.body
if (!(await AccessUtils.canUserAccess(ACCESS_TYPE.REPOSITORY_SET, ctx.session.id, data.id))) {
ctx.body = Consts.COMMON_ERROR_RES.ACCESS_DENY
return
}
try {
await MigrateService.importRepoFromJSON(data, ctx.session.id)
ctx.body = {
isOk: true,
repository: {
id: data.id,
},
}
} catch (error) {
ctx.body = {
isOk: false,
message: '服务器错误,导入失败'
}
throw (error)
}
})
router.get('/:type/history/:itfId', isLoggedIn, async ctx => {
const pager: IPager = {
limit: +ctx.query.limit || 10,
offset: +ctx.query.offset || 0,
}
let type: ENTITY_TYPE
if (ctx.params.type === 'interface') {
type = ENTITY_TYPE.INTERFACE
} else if (ctx.params.type === 'repository') {
type = ENTITY_TYPE.REPOSITORY
} else {
ctx.body = {
isOk: false,
errMsg: 'error path',
}
return
}
ctx.body = {
isOk: true,
data: await RepositoryService.getHistoryLog(+ctx.params.itfId, type, pager)
}
})
router.get('/interface/history/JSONData/:id', isLoggedIn, async ctx => {
const historyLogId = +ctx.params.id
ctx.set('Content-disposition', `attachment; filename=history_log_detail_data_${historyLogId}`)
ctx.set('Content-type', 'text/html; charset=UTF-8')
ctx.body = await RepositoryService.getHistoryLogJSONData(historyLogId)
})
router.get('/interface/backup/JSONData/:id', isLoggedIn, async ctx => {
const itfId = +ctx.params.id
ctx.set('Content-disposition', `attachment; filename=interface_backup_${itfId}`)
ctx.set('Content-type', 'text/html; charset=UTF-8')
ctx.body = await RepositoryService.getInterfaceJSONData(itfId)
})
================================================
FILE: src/routes/router.ts
================================================
import * as Router from 'koa-router'
import { DefaultState, DefaultContext } from "koa"
import config from '../config'
let router = new Router<DefaultState, DefaultContext>({prefix: config.serve.path})
// index
router.get('/', (ctx) => {
ctx.body = 'Hello RAP!'
})
// env
router.get('/env', (ctx) => {
ctx.body = process.env.NODE_ENV
})
// fix preload
router.get('/check.node', (ctx) => {
ctx.body = 'success'
})
router.get('/status.taobao', (ctx) => {
ctx.body = 'success'
})
router.get('/test/test.status', (ctx) => {
ctx.body = 'success'
})
// proxy
router.get('/proxy', async(ctx) => {
let { target } = ctx.query
console.log(` <=> ${target}`)
let json = await fetch(target as string).then(res => res.json())
ctx.type = 'json'
ctx.body = json
})
export default router
================================================
FILE: src/routes/utils/access.ts
================================================
import OrganizationService from '../../service/organization'
import RepositoryService from '../../service/repository'
import { Module, Interface, Property } from '../../models'
export enum ACCESS_TYPE {
ORGANIZATION_GET,
ORGANIZATION_SET,
REPOSITORY_GET,
REPOSITORY_SET,
MODULE_GET,
MODULE_SET,
INTERFACE_GET,
INTERFACE_SET,
PROPERTY_GET,
PROPERTY_SET,
USER,
ADMIN,
}
const inTestMode = process.env.TEST_MODE === 'true'
export class AccessUtils {
public static async canUserAccess(
accessType: ACCESS_TYPE,
curUserId: number,
entityId: number,
token?: string,
): Promise<boolean> {
// 测试模式无权限
if (inTestMode) {
return true
}
// 无 session 且无 toeken 时拒绝访问
if (!curUserId && !token) {
return false
}
if (
accessType === ACCESS_TYPE.ORGANIZATION_GET ||
accessType === ACCESS_TYPE.ORGANIZATION_SET
) {
return OrganizationService.canUserAccessOrganization(curUserId, entityId)
} else if (
accessType === ACCESS_TYPE.REPOSITORY_GET ||
accessType === ACCESS_TYPE.REPOSITORY_SET
) {
return RepositoryService.canUserAccessRepository(curUserId, entityId, token)
} else if (accessType === ACCESS_TYPE.MODULE_GET || accessType === ACCESS_TYPE.MODULE_SET) {
const mod = await Module.findByPk(entityId)
return RepositoryService.canUserAccessRepository(curUserId, mod.repositoryId, token)
} else if (
accessType === ACCESS_TYPE.INTERFACE_GET ||
accessType === ACCESS_TYPE.INTERFACE_SET
) {
const itf = await Interface.findByPk(entityId)
return RepositoryService.canUserAccessRepository(curUserId, itf.repositoryId, token)
} else if (accessType === ACCESS_TYPE.PROPERTY_GET || accessType === ACCESS_TYPE.PROPERTY_SET) {
const p = await Property.findByPk(entityId)
return RepositoryService.canUserAccessRepository(curUserId, p.repositoryId, token)
}
return false
}
public static isAdmin(curUserId: number) {
if (inTestMode) {
return true
}
return curUserId === 1
}
}
================================================
FILE: src/routes/utils/const.ts
================================================
export enum COMMON_MSGS {
ACCESS_DENY = '对不起,您没有访问该数据的权限。 Sorry, you have no access to visit this data.',
}
export const COMMON_ERROR_RES = {
ERROR_PARAMS: { isOk: false, errMsg: '参数错误' },
ACCESS_DENY: { isOk: false, errMsg: '您没有访问权限' },
NOT_LOGIN: { isOk: false, errMsg: '您未登陆,或登陆状态过期。请登陆后重试' },
}
export enum DATE_CONST {
SECOND = 1000,
MINUTE = 1000 * 60,
HOUR = 1000 * 60 * 60,
DAY = 1000 * 60 * 60 * 24,
MONTH = 1000 * 60 * 60 * 24 * 30,
YEAR = 1000 * 60 * 60 * 24 * 365,
}
export enum ENTITY_TYPE {
REPOSITORY = 0,
INTERFACE = 1,
PARAMETER = 2,
}
export enum THEME_TEMPLATE_KEY {
INDIGO = 'INDIGO', // DEFAULT
RED = 'RED',
BLACK = 'BLACK',
BLUE = 'BLUE',
GREEN = 'GREEN',
PINK = 'PINK',
ORANGE = 'ORANGE',
PURPLE = 'PURPLE',
CYAN = 'CYAN',
}
export enum BODY_OPTION {
FORM_DATA = 'FORM_DATA',
FORM_URLENCODED = 'FORM_URLENCODED',
RAW = 'RAW',
BINARY = 'BINARY',
}
================================================
FILE: src/routes/utils/helper.ts
================================================
import { Module, Interface, Property } from '../../models'
import { Repository } from '../../models'
const genExampleModule = (extra: any) => Object.assign({
name: '示例模块',
description: '示例模块',
creatorId: undefined,
repositoryId: undefined,
}, extra)
const genExampleInterface = (extra: any) => Object.assign({
name: '示例接口',
url: `/example/${Date.now()}`,
method: 'GET',
description: '示例接口描述',
creatorId: undefined,
lockerId: undefined,
moduleId: undefined,
repositoryId: undefined,
}, extra)
const genExampleProperty = (extra: any) => Object.assign({
scope: undefined,
name: 'foo',
type: 'String',
rule: '',
value: '@ctitle',
description: ({ request: '请求属性示例', response: '响应属性示例' } as any)[extra.scope],
parentId: -1,
creatorId: undefined,
interfaceId: undefined,
moduleId: undefined,
repositoryId: undefined,
}, extra)
// 初始化仓库
const initRepository = async (repository: Repository) => {
let mod = await Module.create(genExampleModule({
creatorId: repository.creatorId,
repositoryId: repository.id,
}))
await initModule(mod)
}
// 初始化模块
const initModule = async (mod: Module) => {
let itf = await Interface.create(genExampleInterface({
creatorId: mod.creatorId,
moduleId: mod.id,
repositoryId: mod.repositoryId,
}))
await initInterface(itf)
}
// 初始化接口
const initInterface = async (itf: Interface) => {
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,
}))
}
export {
genExampleModule,
genExampleInterface,
initRepository,
initModule,
initInterface,
}
================================================
FILE: src/routes/utils/pagination.ts
================================================
/*
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 )
*/
export default class Pagination {
public data: any
public total: any
public cursor: number
public limit: number
public focus: number
public pages: any
public start: any
public end: any
public hasPrev: any
public hasNext: any
public hasFirst: any
public hasLast: any
public prev: any
public next: any
public first: any
public last: any
constructor(data: any, cursor: any, limit: any) {
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()
}
public calc() {
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
}
public moveTo(cursor: any) {
this.cursor = parseInt(cursor, 10)
return this.calc()
}
public moveToPrev() {
return this.moveTo(this.cursor - 1)
}
public moveToNext() {
return this.moveTo(this.cursor + 1)
}
public moveToFirst() {
return this.moveTo(1)
}
public moveToLast() {
return this.moveTo(this.pages)
}
public fetch(arr: any) {
return (arr || this.data).slice(this.start, this.end)
}
public setData(data: any) {
this.data = data
this.total = data.length
return this.calc()
}
public setTotal(total: any) {
this.total = parseInt(total, 10)
return this.calc()
}
public setCursor(cursor: any) {
this.cursor = parseInt(cursor, 10)
return this.calc()
}
public setFocus(focus: any) {
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(String(this.focus / this.limit), 10) + 1
return this.calc()
}
public setLimit(limit: any) {
this.limit = parseInt(limit, 10)
return this.calc()
}
public get(focus: any) {
if (focus !== undefined) return this.data[focus % this.data.length]
else return this.data[this.focus]
}
public toString() {
return JSON.stringify(this, undefined, 4)
}
public to = this.moveTo
public toPrev = this.moveToPrev
public toNext = this.moveToNext
public toFirst = this.moveToFirst
public toLast = this.moveToLast
}
================================================
FILE: src/routes/utils/tree.ts
================================================
import { Property } from '../../models'
import * as _ from 'underscore'
const { VM } = require('vm2')
import * as Mock from 'mockjs'
const { RE_KEY } = require('mockjs/src/mock/constant')
export default class Tree {
public static ArrayToTree(list: Property[]) {
let result: any = {
name: 'root',
children: [],
depth: 0,
}
let mapped: any = {}
list.forEach(item => {
mapped[item.id] = item
})
function _parseChildren(parentId: any, children: any, depth: any) {
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: number) => {
// 忽略 parentId 为 0 的根属性(历史遗留),现为 -1
if (parentId === -1) return true
return false
},
result.children,
result.depth,
)
return result
}
// TODO 2.x 和前端重复了
public static TreeToTemplate(tree: any) {
const vm = new VM({
sandbox: {},
timeout: 3000
})
function parse(item: any, result: any) {
let rule = item.rule ? '|' + item.rule : ''
let value = item.value
if (
item.value &&
item.value.indexOf('[') === 0 &&
item.value.substring(item.value.length - 1) === ']' &&
!!rule
) {
try {
result[item.name + rule] = vm.run(`(${item.value})`)
} catch (e) {
result[item.name + rule] = item.value
}
} else {
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] = vm.run('(' + item.value + ')')
} 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] = vm.run(`(${item.value})`)
} catch (e) {
result[item.name + rule] = item.value
}
} else {
result[item.name + rule] = {}
item.children.forEach((child: any) => {
parse(child, result[item.name + rule])
})
}
break
case 'Array':
if (item.value) {
try {
result[item.name + rule] = vm.run(`(${item.value})`)
} catch (e) {
result[item.name + rule] = item.value
}
} else {
result[item.name + rule] = item.children.length ? [{}] : []
item.children.forEach((child: any) => {
parse(child, result[item.name + rule][0])
})
}
break
case 'Null':
// tslint:disable-next-line: no-null-keyword
result[item.name + rule] = null
break
}
}
}
let result = {}
tree.children.forEach((child: any) => {
parse(child, result)
})
return result
}
public static TemplateToData(template: any) {
// 数据模板 template 中可能含有攻击代码,例如死循环,所以在沙箱中生成最终数据
// https://nodejs.org/dist/latest-v7.x/docs/api/vm.html
const vm = new VM({
sandbox: { mock: Mock.mock, template, },
timeout: 3000
})
try {
let data: any = vm.run('mock(template)')
let keys = Object.keys(data)
if (keys.length === 1 && keys[0] === '__root__') data = data.__root__
return data
} catch (err) {
console.error(err)
return {}
}
}
public static ArrayToTreeToTemplate(list: Property[]) {
let tree = Tree.ArrayToTree(list)
let template = Tree.TreeToTemplate(tree)
return template
}
public static ArrayToTreeToTemplateToData(list: Property[], extra?: any) {
let tree = Tree.ArrayToTree(list)
let template: { [key: string]: any } = Tree.TreeToTemplate(tree)
let data
const propertyMap: { [key: string]: Property } = {}
for (const p of list) {
propertyMap[p.name] = p
}
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))
const recursivelyFillData = (node: any) => {
for (const key in node) {
if (!node.hasOwnProperty(key)) continue
let data = node[key]
if (_.isObject(data)) {
recursivelyFillData(data)
continue
}
for (const eKey in extra) {
if (!extra.hasOwnProperty(eKey)) continue
const pattern = new RegExp(`\\$${eKey}\\$`, 'g')
if (data && pattern.test(data)) {
let result = data.replace(pattern, extra[eKey])
const p = propertyMap[key]
if (p) {
if (p.type === 'Number') {
result = +result || 1
} else if (p.type === 'Boolean') {
result = result === 'true' || !!+result
}
}
data = node[key] = result
}
}
}
}
recursivelyFillData(scopedData)
data = _.pick(scopedData, keys)
} else {
data = Tree.TemplateToData(template)
}
return data
}
public static ArrayToTreeToTemplateToJSONSchema(list: Property[]) {
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
public static stringifyWithFunctonAndRegExp(json: object) {
return JSON.stringify(
json,
(k, v) => {
k
if (typeof v === 'function') return v.toString()
if (v !== undefined && v !== null && v.exec) return v.toString()
else return v
},
2,
)
}
// 把用户的 mock json 转换成 json-schema 再转换成 properties
public static jsonToArray(
json: any,
{
userId,
repositoryId,
moduleId,
interfaceId,
scope,
}: {
userId: number
repositoryId: number
moduleId: number
interfaceId: number
scope: 'request' | 'response'
},
) {
const isIncreamentNumberSequence = (numbers: any) =>
numbers.every(
(num: any) =>
typeof num === 'number' &&
((num: any, i: number) => i === 0 || num - numbers[i - 1] === 1),
)
function isPrimitiveType(type: string) {
return ['number', 'null', 'undefined', 'boolean', 'string'].indexOf(type.toLowerCase()) > -1
}
function mixItemsProperties(items: any) {
// 合并 item properties 的 key,返回的 item 拥有导入 json 的所有 key
if (!items || !items.length) {
return {
properties: [],
}
} else if (items.length === 1) {
if (!items[0].properties) {
items[0].properties = []
}
return items[0]
} else {
const baseItem = items[0]
if (!baseItem.properties) {
baseItem.properties = []
}
const baseProperties = baseItem.properties
for (let i = 1; i < items.length; ++i) {
const item = items[i]
if (item.properties && item.properties.length) {
for (const p of item.properties) {
if (!baseProperties.find((e: any) => e.name === p.name)) {
baseProperties.push(p)
}
}
}
}
return baseItem
}
}
/** MockJS 的 toJSONSchema 的 bug 会导致有 length 属性的对象被识别成数组
* 众所周知 MockJS 已经不维护了,所以只能自己想想办法
* 先递归把 length 替换成其他的名称,生成 schema 后再换回来
*/
const lengthAlias = '__mockjs_length_*#06#'
const replaceLength = (obj: any) => {
for (const k in obj) {
if (obj[k] && typeof obj[k] === 'object') {
replaceLength(obj[k])
} else {
// Do something with obj[k]
if (k === 'length') {
const v = obj[k]
delete obj[k]
obj[lengthAlias] = v
}
}
}
}
function handleJSONSchema(
schema: any,
parent = { id: -1 },
memoryProperties: any,
siblings?: any,
) {
if (!schema) {
return
}
const hasSiblings = siblings instanceof Array && siblings.length > 0
// DONE 2.1 需要与 Mock 的 rule.type 规则统一,首字符小写,好烦!应该忽略大小写!
if (schema.name === lengthAlias) {
schema.name = 'length'
}
let type = schema.type[0].toUpperCase() + schema.type.slice(1)
let rule = ''
if (type === 'Array' && schema.items && schema.items.length > 1) {
rule = schema.items.length + ''
}
let value = /Array|Object/.test(type) ? '' : schema.template
if (schema.items && schema.items.length) {
const childType = schema.items[0].type
if (isPrimitiveType(childType)) {
value = JSON.stringify(schema.template)
rule = ''
}
} else if (hasSiblings && isPrimitiveType(type)) {
// 如果是简单数据可以在这里进行合并
const valueArr = siblings.map((s: any) => s && s.template)
if (_.uniq(valueArr).length > 1) {
// 只有在数组里有不同元素时再合并
if (isIncreamentNumberSequence(valueArr)) {
// 如果是递增数字序列特殊处理
value = valueArr[0]
rule = '+1'
} else {
// 比如 [{a:1},{a:2}]
// 我们可以用 type: Array rule: +1 value: [1,2] 进行还原
value = JSON.stringify(valueArr)
type = 'Array'
rule = '+1'
}
}
}
type Property = {
name: any
type: any
rule: string
value: any
descripton: string
creator: any
repositoryId: any
moduleId: any
interfaceId: any
scope: any
parentId: number
memory: boolean
id: any
}
const property: Property = Object.assign(
{
name: schema.name,
type,
rule,
value,
descripton: '',
},
{
creator: userId,
repositoryId: repositoryId,
moduleId: moduleId,
interfaceId,
scope,
parentId: parent.id,
},
{
memory: true,
id: _.uniqueId('memory-'),
},
)
memoryProperties.push(property)
if (schema.properties) {
schema.properties.forEach((item: any) => {
const childSiblings = hasSiblings
? siblings.map(
(s: any) =>
(s && s.properties && s.properties.find((p: any) => p && p.name === item.name)) || null,
)
: undefined
handleJSONSchema(item, property, memoryProperties, childSiblings)
})
}
mixItemsProperties(schema.items).properties.forEach((item: any) => {
const siblings = schema.items.map(
(o: any) => o.properties.find((p: any) => p.name === item.name) || null,
)
handleJSONSchema(item, property, memoryProperties, siblings)
})
}
if (JSON.stringify(json).indexOf('length') > -1) {
// 递归查找替换 length 是一个重操作,先进行一次字符串查找,发现存在 length 字符再进行
replaceLength(json)
}
if (json instanceof Array) {
json = { _root_: json }
}
const schema = Mock.toJSONSchema(json)
const memoryProperties: any = []
if (schema.properties) {
schema.properties.forEach((item: any) => handleJSONSchema(item, undefined, memoryProperties))
}
return memoryProperties
}
}
================================================
FILE: src/routes/utils/url.ts
================================================
let pathToRegexp = require('path-to-regexp')
export default class UrlUtils {
public static getRelative = (url: string) => {
url = url.toLowerCase()
const prefixes = ['https://', 'http://']
for (let item of prefixes) {
if (url.indexOf(item) > -1) {
url = url.substring(item.length)
if (url.indexOf('/') > -1) {
url = url.substring(url.indexOf('/'))
} else {
url = '/'
}
break
}
}
if (url.indexOf('?') > -1) {
url = url.substring(0, url.indexOf('?'))
}
if (url[0] !== '/') url = '/' + url
return url
}
// 把 /pet/{id}/ 转换成 /pet/:id/
// https://regexr.com/537jp
public static convertBracePatternRestfulUrl(url: string) {
return url.replace(/\/{([^}]+)}/g, '/:$1')
}
public static urlMatchesPattern = (url: string, pattern: string) => {
url = UrlUtils.getRelative(url)
pattern = UrlUtils.getRelative(pattern)
let re = pathToRegexp(pattern)
return re.test(url)
}
public static getUrlPattern = (pattern: string) => {
pattern = UrlUtils.getRelative(pattern)
pattern = UrlUtils.convertBracePatternRestfulUrl(pattern)
return pathToRegexp(pattern)
}
}
================================================
FILE: src/scripts/app.ts
================================================
// const debug = true
import * as Koa from 'koa'
import * as session from 'koa-generic-session'
import * as redisStore from 'koa-redis'
import * as logger from 'koa-logger'
import * as serve from 'koa-static'
import * as cors from 'kcors'
import * as body from 'koa-body'
import router from '../routes'
import config from '../config'
import { startTask } from '../service/task'
const app = new Koa()
let appAny: any = app
appAny.counter = { users: {}, mock: 0 }
app.keys = config.keys
app.use(
session({
// @ts-ignore
store: redisStore(config.redis)
})
)
if (process.env.NODE_ENV === 'development' && process.env.TEST_MODE !== 'true') app.use(logger())
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*')
ctx.set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS')
ctx.set('Access-Control-Allow-Credentials', 'true')
await next()
if (ctx.path === '/favicon.ico') return
ctx.session.views = (ctx.session.views || 0) + 1
let app: any = ctx.app
if (ctx.session.fullname) 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 = JSON.stringify(ctx.body, undefined, 2)
}
})
app.use(async (ctx, next) => {
await next()
if (ctx.request.query.callback) {
let body = typeof ctx.body === 'object' ? JSON.stringify(ctx.body, undefined, 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({
multipart: true,
formLimit: '10mb',
textLimit: '10mb',
jsonLimit: '10mb',
}),
)
app.use(router.routes())
startTask()
export default app
================================================
FILE: src/scripts/dev.ts
================================================
import config from '../config'
import app from './app'
const start = () => {
let execSync = require('child_process').execSync
let port = config.serve.port
let url = `http://localhost:${port}` // /api.html
let open = false
console.log('----------------------------------------')
app.listen(port, () => {
console.log(`rap2-delos is running as ${url}`)
if (!open) return
try {
execSync(`osascript openChrome.applescript ${url}`, { cwd: __dirname, stdio: 'ignore' })
} catch (e) {
execSync(`open ${url}`)
}
})
}
start()
export {}
================================================
FILE: src/scripts/init/bo.ts
================================================
import { mock } from 'mockjs'
const scopes = ['request', 'response']
const methods = ['GET', 'POST', 'PUT', 'DELETE']
const types = ['String', 'Number', 'Boolean', 'Object', 'Array', 'Function', 'RegExp', 'Null']
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
export const BO_ADMIN = { id: USER_ID++, fullname: 'admin', email: 'admin@rap2.com', password: 'admin' }
export const BO_MOZHI = { id: USER_ID++, fullname: '墨智', email: 'mozhi@rap2.com', password: 'mozhi' }
export const BO_USER_COUNT = 10
export const BO_USER_FN = () => mock({
id: USER_ID++,
fullname: '@cname',
email: '@email',
password: '@word(6)',
})
export const BO_ORGANIZATION_COUNT = 3
export const BO_ORGANIZATION_FN = (source: any) => {
return Object.assign(
mock({
id: ORGANIZATION_ID++,
name: '组织@ctitle(5)',
description: '@cparagraph',
logo: '@url',
creatorId: undefined,
owner: undefined,
members: '',
}),
source,
)
}
export const BO_REPOSITORY_COUNT = 3
export const BO_REPOSITORY_FN = (source: any) => {
return Object.assign(
mock({
id: REPOSITORY_ID++,
name: '仓库@ctitle',
description: '@cparagraph',
logo: '@url',
}),
source,
)
}
export const BO_MODULE_COUNT = 3
export const BO_MODULE_FN = (source: any) => {
return Object.assign(
mock({
id: MODULE_ID++,
name: '模块@ctitle(4)',
description: '@cparagraph',
repositoryId: undefined,
creatorId: undefined,
}),
source,
)
}
export const BO_INTERFACE_COUNT = 3
export const BO_INTERFACE_FN = (source: any) => {
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,
)
}
export const BO_PROPERTY_COUNT = 6
export const BO_PROPERTY_FN = (source: any) => {
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,
)
}
================================================
FILE: src/scripts/init/delos.ts
================================================
import sequelize from '../../models/sequelize'
import { User, Organization, Repository, Module, Interface, Property } from '../../models/index'
import { BO_ADMIN, BO_MOZHI } from './bo'
import { BO_USER_FN, BO_ORGANIZATION_FN, BO_REPOSITORY_FN, BO_MODULE_FN, BO_INTERFACE_FN, BO_PROPERTY_FN } from './bo'
import { BO_USER_COUNT, BO_ORGANIZATION_COUNT, BO_REPOSITORY_COUNT, BO_MODULE_COUNT, BO_INTERFACE_COUNT, BO_PROPERTY_COUNT } from './bo'
const EMPTY_WHERE = { where: {} }
export 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.$set('members', 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.$set('members', (
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.$set('members', (
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.$set('members', users.filter(user => user.id !== BO_ADMIN.id))
await initRepository(repository)
}
}
}
async function initRepository (repository: any) {
// 模块
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.$add('interfaces', 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.$add('properties', prop)
}
}
}
}
export 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.findByPk(BO_ADMIN.id)
// for (let k in admin) console.log(k)
let owned: any = await admin.$get('ownedOrganizations')
console.log(owned.map((item: any) => item.toJSON()))
let mozhi = await User.findByPk(BO_MOZHI.id)
for (let k in mozhi) console.log(k)
let joined: any = await mozhi.$get('joinedOrganizations')
console.log(joined.map((item: any) => item.toJSON()))
}
================================================
FILE: src/scripts/init/index.ts
================================================
import { init, after } from './delos'
/**
* initialize database
*/
export async function main () {
await init()
console.log('after init')
await after()
console.log('after after')
}
main().then(() => {
console.log('Run create-db finished successfully.')
process.exit(0)
})
================================================
FILE: src/scripts/initSchema.ts
================================================
import sequelize from "../models/sequelize"
sequelize
.sync({
force: true,
})
.then(() => {
console.log("成功初始化 DB Schema")
process.exit(0)
})
.catch(e => {
console.log("初始化 DB Schema 中遇到了错误")
console.log(e)
process.exit(0)
})
================================================
FILE: src/scripts/openChrome.applescript
================================================
(*
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
================================================
FILE: src/scripts/rap2_delos.sql
================================================
-- 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
================================================
FILE: src/scripts/setToken.ts
================================================
// 补数据 token
import nanoid = require("nanoid")
import sequelize from "../models/sequelize"
import { Repository } from "
gitextract_mkcp7_61/ ├── .dockerignore ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── ---------.md │ └── bug--.md ├── .gitignore ├── .jshintrc ├── .prettierrc ├── .travis.yml ├── .vscode/ │ └── launch.json ├── APP-META/ │ └── docker-config/ │ └── environment/ │ └── cai/ │ └── conf/ │ └── nginx-proxy.conf ├── Dockerfile ├── LICENSE ├── README.md ├── database/ │ ├── history/ │ │ ├── patch-null-type.sql │ │ ├── v2.5_change.sql │ │ ├── v2.6_change_20180705.sql │ │ ├── v2.8.0_token.sql │ │ ├── v2018_07_27.sql │ │ ├── v2018_08_27_add_property_required.sql │ │ └── v2019_09_26_default_val_table.sql │ ├── recreate_db.sql │ ├── v2018_05_15.sql │ ├── v2020_07_06_history_log.sql │ ├── v2020_07_21_body_option_save.sql │ └── v2020_07_21_merge_PR.sql ├── docker-compose.yml ├── docs/ │ └── Schedule.md ├── package.json ├── public/ │ ├── 404.json │ ├── 500.json │ ├── error.html │ ├── index.html │ ├── libs/ │ │ ├── README.md │ │ ├── fetch.rap.js │ │ ├── jquery.rap.js │ │ └── mock.rap.js │ └── test/ │ ├── index.html │ ├── test.plugin.fetch.html │ ├── test.plugin.jquery.html │ ├── test.plugin.mock.html │ └── test.request.js ├── release/ │ └── v2.6_20180705.md ├── src/ │ ├── config/ │ │ ├── config.dev.ts │ │ ├── config.local.ts │ │ ├── config.prod.ts │ │ └── index.ts │ ├── dispatch.ts │ ├── helpers/ │ │ ├── dedent.ts │ │ └── pandoc.ts │ ├── models/ │ │ ├── bo/ │ │ │ ├── defaultVal.ts │ │ │ ├── historyLog.ts │ │ │ ├── interface.ts │ │ │ ├── logger.ts │ │ │ ├── module.ts │ │ │ ├── notification.ts │ │ │ ├── organization.ts │ │ │ ├── organizationsMembers.ts │ │ │ ├── property.ts │ │ │ ├── repositoriesCollaborators.ts │ │ │ ├── repositoriesMembers.ts │ │ │ ├── repository.ts │ │ │ └── user.ts │ │ ├── helper.ts │ │ ├── index.ts │ │ ├── sequelize.ts │ │ └── util/ │ │ ├── helper.ts │ │ └── queryInclude.ts │ ├── routes/ │ │ ├── account.ts │ │ ├── analytics.ts │ │ ├── base.ts │ │ ├── counter.ts │ │ ├── export.ts │ │ ├── index.ts │ │ ├── migration.ts │ │ ├── mock.ts │ │ ├── organization.ts │ │ ├── repository.ts │ │ ├── router.ts │ │ └── utils/ │ │ ├── access.ts │ │ ├── const.ts │ │ ├── helper.ts │ │ ├── pagination.ts │ │ ├── tree.ts │ │ └── url.ts │ ├── scripts/ │ │ ├── app.ts │ │ ├── dev.ts │ │ ├── init/ │ │ │ ├── bo.ts │ │ │ ├── delos.ts │ │ │ └── index.ts │ │ ├── initSchema.ts │ │ ├── openChrome.applescript │ │ ├── rap2_delos.sql │ │ ├── setToken.ts │ │ ├── updateSchema.ts │ │ └── worker.ts │ ├── service/ │ │ ├── export/ │ │ │ ├── docx.ts │ │ │ ├── markdown.ts │ │ │ ├── pdf.ts │ │ │ └── postman.ts │ │ ├── mail.ts │ │ ├── migrate.ts │ │ ├── mock.ts │ │ ├── organization.ts │ │ ├── redis.ts │ │ ├── repository.ts │ │ ├── task.ts │ │ └── utils.ts │ └── types/ │ ├── custom-typings.d.ts │ ├── index.d.ts │ └── postman.d.ts ├── test/ │ ├── helper.js │ ├── index.js │ ├── test.account.js │ ├── test.counter.js │ ├── test.interface.js │ ├── test.js │ ├── test.mock.js │ ├── test.module.js │ ├── test.organization.js │ ├── test.property.js │ └── test.repository.js ├── tsconfig.json └── tslint.json
SYMBOL INDEX (212 symbols across 44 files)
FILE: database/history/v2019_09_26_default_val_table.sql
type `default_val` (line 1) | CREATE TABLE `default_val` (
FILE: database/v2020_07_06_history_log.sql
type `history_log` (line 1) | CREATE TABLE `history_log` (
FILE: src/helpers/dedent.ts
function dedent (line 6) | function dedent(
FILE: src/helpers/pandoc.ts
function pandoc (line 3) | function pandoc(from: string, to: string, ...args: string[]) {
FILE: src/models/bo/defaultVal.ts
class DefaultVal (line 6) | class DefaultVal extends Model<DefaultVal> {
FILE: src/models/bo/historyLog.ts
class HistoryLog (line 7) | class HistoryLog extends Model<HistoryLog> {
constant LOG_SEPERATOR (line 53) | const LOG_SEPERATOR = '.|.'
constant LOG_SUB_SEPERATOR (line 54) | const LOG_SUB_SEPERATOR = '@|@'
FILE: src/models/bo/interface.ts
type methods (line 9) | enum methods { GET = 'GET', POST = 'POST', PUT = 'PUT', DELETE = 'DELETE' }
type MoveOp (line 11) | enum MoveOp {
class Interface (line 17) | class Interface extends Model<Interface> {
method deleteCache (line 23) | static async deleteCache(instance: Interface) {
method bulkDeleteCache (line 30) | static async bulkDeleteCache(options: any) {
FILE: src/models/bo/logger.ts
type types (line 4) | enum types {
class Logger (line 10) | class Logger extends Model<Logger> {
FILE: src/models/bo/module.ts
class Module (line 9) | class Module extends Model<Module> {
method deleteCache (line 14) | static async deleteCache(instance: Module) {
method bulkDeleteCache (line 21) | static async bulkDeleteCache(options: any) {
FILE: src/models/bo/notification.ts
class Notification (line 4) | class Notification extends Model<Notification> {
FILE: src/models/bo/organization.ts
class Organization (line 5) | class Organization extends Model<Organization> {
method createLog (line 9) | static async createLog(instance: Organization) {
FILE: src/models/bo/organizationsMembers.ts
class OrganizationsMembers (line 5) | class OrganizationsMembers extends Model<OrganizationsMembers> {
FILE: src/models/bo/property.ts
type SCOPES (line 4) | enum SCOPES { REQUEST = 'request', RESPONSE = 'response', SCRIPT = 'scri...
type TYPES (line 5) | enum TYPES { STRING = 'String', NUMBER = 'Number', BOOLEAN = 'Boolean', ...
class Property (line 8) | class Property extends Model<Property> {
type POS_TYPE (line 103) | enum POS_TYPE {
FILE: src/models/bo/repositoriesCollaborators.ts
class RepositoriesCollaborators (line 5) | class RepositoriesCollaborators extends Model<RepositoriesCollaborators> {
FILE: src/models/bo/repositoriesMembers.ts
class RepositoriesMembers (line 5) | class RepositoriesMembers extends Model<RepositoriesMembers> {
FILE: src/models/bo/repository.ts
class Repository (line 6) | class Repository extends Model<Repository> {
method cleanCache (line 12) | static async cleanCache(instance: Repository) {
method bulkDeleteCache (line 19) | static async bulkDeleteCache(options: any) {
FILE: src/models/bo/user.ts
class User (line 5) | class User extends Model<User> {
FILE: src/models/util/helper.ts
type IHelper (line 1) | interface IHelper {
FILE: src/models/util/queryInclude.ts
type IQueryInclude (line 11) | interface IQueryInclude {
FILE: src/routes/account.ts
constant NOTIFICATION_EXCLUDE_ATTRIBUTES (line 250) | let NOTIFICATION_EXCLUDE_ATTRIBUTES: any = []
FILE: src/routes/analytics.ts
constant SELECT (line 7) | const SELECT = { type: Sequelize.QueryTypes.SELECT }
constant YYYY_MM_DD (line 10) | const YYYY_MM_DD = 'YYYY-MM-DD'
FILE: src/routes/base.ts
function isLoggedIn (line 6) | async function isLoggedIn(ctx: ParameterizedContext<any, any>, next: () ...
FILE: src/routes/utils/access.ts
type ACCESS_TYPE (line 5) | enum ACCESS_TYPE {
class AccessUtils (line 21) | class AccessUtils {
method canUserAccess (line 22) | public static async canUserAccess(
method isAdmin (line 64) | public static isAdmin(curUserId: number) {
FILE: src/routes/utils/const.ts
type COMMON_MSGS (line 1) | enum COMMON_MSGS {
constant COMMON_ERROR_RES (line 5) | const COMMON_ERROR_RES = {
type DATE_CONST (line 11) | enum DATE_CONST {
type ENTITY_TYPE (line 21) | enum ENTITY_TYPE {
type THEME_TEMPLATE_KEY (line 27) | enum THEME_TEMPLATE_KEY {
type BODY_OPTION (line 39) | enum BODY_OPTION {
FILE: src/routes/utils/pagination.ts
class Pagination (line 48) | class Pagination {
method constructor (line 66) | constructor(data: any, cursor: any, limit: any) {
method calc (line 74) | public calc() {
method moveTo (line 111) | public moveTo(cursor: any) {
method moveToPrev (line 116) | public moveToPrev() {
method moveToNext (line 120) | public moveToNext() {
method moveToFirst (line 124) | public moveToFirst() {
method moveToLast (line 128) | public moveToLast() {
method fetch (line 132) | public fetch(arr: any) {
method setData (line 136) | public setData(data: any) {
method setTotal (line 142) | public setTotal(total: any) {
method setCursor (line 147) | public setCursor(cursor: any) {
method setFocus (line 152) | public setFocus(focus: any) {
method setLimit (line 160) | public setLimit(limit: any) {
method get (line 165) | public get(focus: any) {
method toString (line 170) | public toString() {
FILE: src/routes/utils/tree.ts
class Tree (line 7) | class Tree {
method ArrayToTree (line 8) | public static ArrayToTree(list: Property[]) {
method TreeToTemplate (line 46) | public static TreeToTemplate(tree: any) {
method TemplateToData (line 136) | public static TemplateToData(template: any) {
method ArrayToTreeToTemplate (line 154) | public static ArrayToTreeToTemplate(list: Property[]) {
method ArrayToTreeToTemplateToData (line 160) | public static ArrayToTreeToTemplateToData(list: Property[], extra?: an...
method ArrayToTreeToTemplateToJSONSchema (line 210) | public static ArrayToTreeToTemplateToJSONSchema(list: Property[]) {
method stringifyWithFunctonAndRegExp (line 220) | public static stringifyWithFunctonAndRegExp(json: object) {
method jsonToArray (line 234) | public static jsonToArray(
FILE: src/routes/utils/url.ts
class UrlUtils (line 3) | class UrlUtils {
method convertBracePatternRestfulUrl (line 28) | public static convertBracePatternRestfulUrl(url: string) {
FILE: src/scripts/init/bo.ts
constant USER_ID (line 8) | let USER_ID = 100000000
constant ORGANIZATION_ID (line 9) | let ORGANIZATION_ID = 1
constant REPOSITORY_ID (line 10) | let REPOSITORY_ID = 1
constant MODULE_ID (line 11) | let MODULE_ID = 1
constant INTERFACE_ID (line 12) | let INTERFACE_ID = 1
constant PROPERTY_ID (line 13) | let PROPERTY_ID = 1
constant BO_ADMIN (line 15) | const BO_ADMIN = { id: USER_ID++, fullname: 'admin', email: 'admin@rap...
constant BO_MOZHI (line 17) | const BO_MOZHI = { id: USER_ID++, fullname: '墨智', email: 'mozhi@rap2.com...
constant BO_USER_COUNT (line 19) | const BO_USER_COUNT = 10
constant BO_ORGANIZATION_COUNT (line 28) | const BO_ORGANIZATION_COUNT = 3
constant BO_REPOSITORY_COUNT (line 44) | const BO_REPOSITORY_COUNT = 3
constant BO_MODULE_COUNT (line 58) | const BO_MODULE_COUNT = 3
constant BO_INTERFACE_COUNT (line 71) | const BO_INTERFACE_COUNT = 3
constant BO_PROPERTY_COUNT (line 88) | const BO_PROPERTY_COUNT = 6
FILE: src/scripts/init/delos.ts
constant EMPTY_WHERE (line 7) | const EMPTY_WHERE = { where: {} }
function init (line 9) | async function init () {
function initRepository (line 71) | async function initRepository (repository: any) {
function after (line 95) | async function after () {
FILE: src/scripts/init/index.ts
function main (line 5) | async function main () {
FILE: src/scripts/rap2_delos.sql
type `interfaces` (line 25) | CREATE TABLE `interfaces` (
type `loggers` (line 57) | CREATE TABLE `loggers` (
type `modules` (line 89) | CREATE TABLE `modules` (
type `notifications` (line 113) | CREATE TABLE `notifications` (
type `organizations` (line 136) | CREATE TABLE `organizations` (
type `repositories_collaborators` (line 160) | CREATE TABLE `repositories_collaborators` (
type `organizations_members` (line 179) | CREATE TABLE `organizations_members` (
type `properties` (line 198) | CREATE TABLE `properties` (
type `repositories` (line 233) | CREATE TABLE `repositories` (
type `repositories_members` (line 263) | CREATE TABLE `repositories_members` (
type `users` (line 282) | CREATE TABLE `users` (
FILE: src/service/export/docx.ts
class DocxService (line 6) | class DocxService {
method export (line 7) | public static async export(repositoryId: number, origin: string): Prom...
FILE: src/service/export/markdown.ts
class PostmanService (line 25) | class PostmanService {
method export (line 26) | public static async export(repositoryId: number, origin: string): Prom...
FILE: src/service/export/postman.ts
constant SCHEMA_V_2_1_0 (line 7) | const SCHEMA_V_2_1_0 = 'https://schema.getpostman.com/json/collection/v2...
class PostmanService (line 9) | class PostmanService {
method export (line 10) | public static async export(repositoryId: number): Promise<PostmanColle...
function getBody (line 80) | function getBody(pList: Property[]) {
function getQuery (line 88) | function getQuery(pList: Property[]) {
function getHeader (line 93) | function getHeader(pList: Property[]) {
function getEvent (line 98) | function getEvent(pList: Property[]) {
FILE: src/service/mail.ts
class MailService (line 4) | class MailService {
method sendMail (line 5) | public static async sendMail(mailOptions: nodemailer.SendMailOptions) {
method send (line 25) | public static send(to: string | string[], subject: string, html: strin...
FILE: src/service/migrate.ts
constant SWAGGER_VERSION (line 16) | const SWAGGER_VERSION = {
constant REQUEST_TYPE_POS (line 92) | const REQUEST_TYPE_POS = {
class MigrateService (line 371) | class MigrateService {
method importRepoFromRAP1ProjectData (line 372) | public static async importRepoFromRAP1ProjectData(
method checkAndFix (line 458) | public static checkAndFix(): void {
method checkPasswordMd5 (line 463) | static async checkPasswordMd5() {
method importRepoFromRAP1DocUrl (line 480) | public static async importRepoFromRAP1DocUrl(
method swaggerToModelRequest (line 510) | public static async swaggerToModelRequest(
method swaggerToModelRespnse (line 580) | public static async swaggerToModelRespnse(
method importRepoFromSwaggerProjectData (line 633) | public static async importRepoFromSwaggerProjectData(
method importRepoFromSwaggerDocUrl (line 1063) | public static async importRepoFromSwaggerDocUrl(
method importInterfaceFromJSON (line 1179) | public static async importInterfaceFromJSON(data: any, curUserId: numb...
method importRepoFromJSON (line 1241) | public static async importRepoFromJSON(data: JsonData, curUserId: numb...
function getMethodFromRAP1RequestType (line 1365) | function getMethodFromRAP1RequestType(type: number) {
type JsonData (line 1380) | interface JsonData {
type OldParameter (line 1422) | interface OldParameter {
type SwaggerParameter (line 1434) | interface SwaggerParameter {
type SwaggerTag (line 1461) | interface SwaggerTag {
type SwaggerInfo (line 1466) | interface SwaggerInfo {
type SwaggerData (line 1472) | interface SwaggerData {
FILE: src/service/mock.ts
constant REG_URL_METHOD (line 8) | const REG_URL_METHOD = /^\/?(get|post|delete|put)/i
class MockService (line 11) | class MockService {
method mock (line 12) | public static async mock(ctx: any, option: { forceVerify: boolean } = ...
FILE: src/service/organization.ts
class OrganizationService (line 4) | class OrganizationService {
method canUserAccessOrganization (line 5) | public static canUserAccessOrganization(userId: number, organizationId...
method getAllOrganizationIdList (line 26) | public static getAllOrganizationIdList(curUserId: number, pager: Pagin...
method getAllOrganizationIdListNum (line 52) | public static getAllOrganizationIdListNum(curUserId: number): Promise<...
FILE: src/service/redis.ts
type CACHE_KEY (line 6) | enum CACHE_KEY {
constant DEFAULT_CACHE_VAL (line 15) | const DEFAULT_CACHE_VAL = {
class RedisService (line 19) | class RedisService {
method getCacheKey (line 22) | private static getCacheKey(key: CACHE_KEY, entityId?: number): string {
method getCache (line 26) | public static getCache(key: CACHE_KEY, entityId?: number): Promise<str...
method setCache (line 38) | public static setCache(key: CACHE_KEY, val: string, entityId?: number,...
method delCache (line 50) | public static delCache(key: CACHE_KEY, entityId?: number): Promise<boo...
FILE: src/service/repository.ts
class RepositoryService (line 10) | class RepositoryService {
method canUserAccessRepository (line 11) | public static async canUserAccessRepository(
method canUserMoveInterface (line 30) | public static async canUserMoveInterface(
method canUserMoveModule (line 43) | public static async canUserMoveModule(userId: number, modId: number, d...
method moveModule (line 50) | public static async moveModule(op: MoveOp, modId: number, destRepoId: ...
method moveInterface (line 99) | public static async moveInterface(
method addHistoryLog (line 159) | public static async addHistoryLog(log: Partial<HistoryLog>) {
method getHistoryLog (line 163) | public static async getHistoryLog(entityId: number, entityType: ENTITY...
method getHistoryLogJSONData (line 193) | public static async getHistoryLogJSONData(id: number) {
method getInterfaceJSONData (line 197) | public static async getInterfaceJSONData(id: number) {
FILE: src/service/task.ts
function startTask (line 6) | async function startTask() {
FILE: src/service/utils.ts
class Utils (line 1) | class Utils {
method escapeSQL (line 2) | public static escapeSQL(str: string) {
FILE: src/types/index.d.ts
type RedisAndClusterOptions (line 7) | interface RedisAndClusterOptions extends RedisOptions {
type IConfigOptions (line 14) | interface IConfigOptions {
type IPager (line 31) | interface IPager {
FILE: src/types/postman.d.ts
type NameOfTheCollection (line 10) | type NameOfTheCollection = string
type DefinitionsDescription (line 14) | type DefinitionsDescription = Description | string | null
type CollectionVersion (line 18) | type CollectionVersion =
type Items1 (line 40) | type Items1 = Item | Folder
type Variable (line 44) | type Variable =
type VariableList (line 58) | type VariableList = Variable[]
type Url (line 62) | type Url =
type Host (line 104) | type Host = string | string[]
type EventList (line 108) | type EventList = Event[]
type Request1 (line 112) | type Request1 = Request | string
type AwsSignatureV4 (line 116) | type AwsSignatureV4 = Auth1[]
type BasicAuthentication (line 120) | type BasicAuthentication = Auth1[]
type BearerTokenAuthentication (line 124) | type BearerTokenAuthentication = Auth1[]
type DigestAuthentication (line 128) | type DigestAuthentication = Auth1[]
type HawkAuthentication (line 132) | type HawkAuthentication = Auth1[]
type NtlmAuthentication (line 136) | type NtlmAuthentication = Auth1[]
type OAuth1 (line 140) | type OAuth1 = Auth1[]
type OAuth2 (line 144) | type OAuth2 = Auth1[]
type HeaderList (line 148) | type HeaderList = Header[]
type FormParameter (line 149) | type FormParameter =
type ResponseTime (line 175) | type ResponseTime = null | string | number
type Headers (line 176) | type Headers = Header2 | string
type Header1 (line 177) | type Header1 = string
type Header2 (line 181) | type Header2 = (Header | Header1)[]
type Responses (line 182) | type Responses = Response[]
type Items (line 183) | type Items = Item | Folder
type PostmanCollection (line 185) | interface PostmanCollection {
type Information (line 199) | interface Information {
type Description (line 214) | interface Description {
type Item (line 235) | interface Item {
type Event (line 254) | interface Event {
type Script (line 273) | interface Script {
type QueryParam (line 290) | interface QueryParam {
type Request (line 300) | interface Request {
type Auth (line 352) | interface Auth {
type Auth1 (line 368) | interface Auth1 {
type ProxyConfig (line 377) | interface ProxyConfig {
type Certificate (line 403) | interface Certificate {
type Header (line 445) | interface Header {
type UrlEncodedParameter (line 461) | interface UrlEncodedParameter {
type Response (line 471) | interface Response {
type Cookie (line 497) | interface Cookie {
type Folder (line 544) | interface Folder {
Condensed preview — 122 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (340K chars).
[
{
"path": ".dockerignore",
"chars": 232,
"preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/cov"
},
{
"path": ".github/ISSUE_TEMPLATE/---------.md",
"chars": 118,
"preview": "---\nname: 产品功能需求及建议\nabout: 为RAP2提供新功能的建议\n\n---\n\n**您提出的功能是否和您遇到的问题有关,请描述该问题**\n\n\n**您是否有建议的实现方案**\n\n\n**其它帮助我们理解您需求的描述、截图**\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug--.md",
"chars": 192,
"preview": "---\nname: Bug反馈\nabout: 为了更快速的定位您的问题,请提供详细的BUG描述。\n\n---\n\n**BUG描述**\n\n\n**复现步骤**\n\n\n**期望结果**\n\n\n**实际结果**\n\n\n**截图**\n\n\n**环境**\n* 是否"
},
{
"path": ".gitignore",
"chars": 143,
"preview": ".nyc_output\n/dist\n/bin\n.DS_Store\nnode_modules\nbower_components\ncoverage\nnpm-debug.log\ntmp\npackage-lock.json\ndump.rdb\n/do"
},
{
"path": ".jshintrc",
"chars": 5542,
"preview": "{\n // JSHint Default Configuration File (as on JSHint website)\n // See http://jshint.com/docs/ for more details\n\n "
},
{
"path": ".prettierrc",
"chars": 107,
"preview": "{\n \"semi\": false,\n \"trailingComma\": \"all\",\n \"singleQuote\": true,\n \"printWidth\": 100,\n \"tabWidth\": 2\n}\n"
},
{
"path": ".travis.yml",
"chars": 431,
"preview": "language: node_js\n\nservices:\n - mysql\n - redis-server\n\ncache:\n directories:\n - node_modules\n - $HOME/.npm\n\nnoti"
},
{
"path": ".vscode/launch.json",
"chars": 353,
"preview": "{\n // 使用 IntelliSense 以学习相关的 Node.js 调试属性。\n // 悬停以查看现有属性的描述。\n // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid"
},
{
"path": "APP-META/docker-config/environment/cai/conf/nginx-proxy.conf",
"chars": 590,
"preview": "server {\n listen 80 default_server;\n server_name www.taobao.com;\n\n location / {\n proxy_pass"
},
{
"path": "Dockerfile",
"chars": 972,
"preview": "# BUILDING\nFROM node:lts-alpine AS builder\n\n# base on work of llitfkitfk@gmail.com\nLABEL maintainer=\"chibing.fy@alibaba-"
},
{
"path": "LICENSE",
"chars": 1060,
"preview": "MIT License\n\nCopyright (c) 2018 THX\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
},
{
"path": "README.md",
"chars": 4459,
"preview": "# RAP2-DELOS 开源社区版本 (后端 API 服务器)\n\n**尊敬的用户:**\n\n由于近期安全审查发现数据风险严重隐患,经慎重评估,我们决定对 rap2.taobao.org 官方体验系统进行下线处理。\n\n> [!WARNING]"
},
{
"path": "database/history/patch-null-type.sql",
"chars": 135,
"preview": "ALTER TABLE `Properties`\n MODIFY COLUMN `type` enum('String','Number','Boolean','Object','Array','Function','RegExp','N"
},
{
"path": "database/history/v2.5_change.sql",
"chars": 248,
"preview": "ALTER TABLE repositories_collaborators\n\tDROP COLUMN createdat ;\n\nALTER TABLE repositories_collaborators\n\tDROP COLUMN upd"
},
{
"path": "database/history/v2.6_change_20180705.sql",
"chars": 141,
"preview": "ALTER TABLE `properties`\n ADD COLUMN `pos` INT(10) NULL DEFAULT 2;\n\nALTER TABLE `interfaces`\n ADD COLUMN `status` INT("
},
{
"path": "database/history/v2.8.0_token.sql",
"chars": 102,
"preview": "# 给 Repositories 表添加 token 列\n# 2019-11-11\nALTER TABLE Repositories \nADD COLUMN token VARCHAR(32) NULL;"
},
{
"path": "database/history/v2018_07_27.sql",
"chars": 70,
"preview": "ALTER TABLE Interfaces\n MODIFY COLUMN `method` VARCHAR(256) NOT NULL;"
},
{
"path": "database/history/v2018_08_27_add_property_required.sql",
"chars": 79,
"preview": "ALTER TABLE `Properties`\n ADD COLUMN `required` TINYINT(1) NOT NULL DEFAULT 0;"
},
{
"path": "database/history/v2019_09_26_default_val_table.sql",
"chars": 475,
"preview": "CREATE TABLE `default_val` (\n\t`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',\n\t`createdat` datetime NOT NULL "
},
{
"path": "database/recreate_db.sql",
"chars": 122,
"preview": "DROP DATABASE RAP2_DELOS_APP;\nCREATE DATABASE IF NOT EXISTS RAP2_DELOS_APP\n\tDEFAULT CHARSET utf8\n\tCOLLATE utf8_general_c"
},
{
"path": "database/v2018_05_15.sql",
"chars": 208,
"preview": "ALTER TABLE Interfaces\nMODIFY priority BIGINT(11) NOT NULL DEFAULT 1;\n\nALTER TABLE Properties\nMODIFY priority BIGINT(11)"
},
{
"path": "database/v2020_07_06_history_log.sql",
"chars": 565,
"preview": "CREATE TABLE `history_log` (\n `id` int NOT NULL AUTO_INCREMENT,\n `entityType` int NOT NULL,\n `entityId` int NOT NULL,"
},
{
"path": "database/v2020_07_21_body_option_save.sql",
"chars": 69,
"preview": "ALTER TABLE `interfaces`\n ADD COLUMN `bodyOption` VARCHAR(255) NULL;"
},
{
"path": "database/v2020_07_21_merge_PR.sql",
"chars": 209,
"preview": "ALTER TABLE `RAP2_DELOS_APP`.`Properties`\nMODIFY COLUMN `scope` enum('request','response','script') CHARACTER SET utf8 C"
},
{
"path": "docker-compose.yml",
"chars": 1785,
"preview": "# mail@dongguochao.com\n# llitfkitfk@gmail.com\n# chibing.fy@alibaba-inc.com\n\nversion: \"3\"\n\nservices:\n # frontend\n dolor"
},
{
"path": "docs/Schedule.md",
"chars": 5159,
"preview": "## 2017.05.15~05.26 计划\n1. RAP1 数据迁移测试\n2. 发布线上服务,自测在项目中的体验\n3. 编写公开 API 的文档(注释)\n4. 其他参与的同学参照 v2.1 需求和约定 http://gitlab.alib"
},
{
"path": "package.json",
"chars": 3346,
"preview": "{\n \"name\": \"rap2-delos\",\n \"version\": \"2.9.0\",\n \"repository\": {\n \"url\": \"https://github.com/thx/rap2-delos\"\n },\n "
},
{
"path": "public/404.json",
"chars": 101,
"preview": "{ \"isOk\": false, \"errMsg\": \"找不到接口,请检查您的配置。 Can not find your API, please check your configurations.\"}"
},
{
"path": "public/500.json",
"chars": 131,
"preview": "{ \"isOk\": false, \"errMsg\": \"服务器内部错误,请联系管理员。 Server side internal error occurred, please contact the administrator of thi"
},
{
"path": "public/error.html",
"chars": 290,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-wid"
},
{
"path": "public/index.html",
"chars": 387,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-wid"
},
{
"path": "public/libs/README.md",
"chars": 184,
"preview": "## RAP2 提供两种拦截方式\n\n1. 引入 `libs/jquery.rap.js`\n 用复写的 `jQuery.ajax` 拦截与 RAP2 接口设置匹配的 ajax 请求,然后转发至 RAP2。\n2. 引入 `libs/mock."
},
{
"path": "public/libs/fetch.rap.js",
"chars": 1221,
"preview": ";(function (RAP, fetch) {\n if (!fetch) {\n console.warn('当前环境不支持 fetch')\n return\n }\n if (!RAP) {\n console.war"
},
{
"path": "public/libs/jquery.rap.js",
"chars": 1692,
"preview": ";(function (RAP, jQuery) {\n if (!jQuery) {\n console.warn('请先引入 jQuery')\n return\n }\n if (!RAP) {\n console.war"
},
{
"path": "public/libs/mock.rap.js",
"chars": 467,
"preview": ";(function (RAP, Mock) {\n if (!RAP) {\n console.warn('请先引入 RAP 插件')\n return\n }\n if (!Mock) {\n console.warn('请"
},
{
"path": "public/test/index.html",
"chars": 179,
"preview": "<ul>\n <li><a href='./test.plugin.jquery.html'>jQuery</a></li>\n <li><a href='./test.plugin.mock.html'>Mock</a></li>\n <"
},
{
"path": "public/test/test.plugin.fetch.html",
"chars": 504,
"preview": "<script src=\"//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js\"></script>\n<script src=\"//g.alicdn.com/thx"
},
{
"path": "public/test/test.plugin.jquery.html",
"chars": 491,
"preview": "<script src=\"//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js\"></script>\n<script src=\"//g.alicdn.com/thx"
},
{
"path": "public/test/test.plugin.mock.html",
"chars": 490,
"preview": "<script src=\"//g.alicdn.com/thx/brix-deps/jquery@jquery/1.12.4/dist/jquery.js\"></script>\n<script src=\"//g.alicdn.com/thx"
},
{
"path": "public/test/test.request.js",
"chars": 784,
"preview": "/* global $ */\nconst appendData = (repositoryId, itf, data) => {\n $('#result').append(`<div>\n <strong>#${repositoryI"
},
{
"path": "release/v2.6_20180705.md",
"chars": 115,
"preview": "# V2.6 更新说明 2018-7-5\n\n* 数据库字段变更请执行 `datbase/v2.6***`\n* 因字段变更,需要清空redis缓存\n 1. 运行redis-cli\n 2. 执行 `flushall` 命令清空缓存"
},
{
"path": "src/config/config.dev.ts",
"chars": 1221,
"preview": "import { IConfigOptions } from '../types'\n\nconst config: IConfigOptions = {\n version: 'v2.9.0',\n serve: {\n port: (p"
},
{
"path": "src/config/config.local.ts",
"chars": 1027,
"preview": "import { IConfigOptions } from \"../types\"\n\nlet config: IConfigOptions = {\n version: '2.9.0',\n serve: {\n port: (proc"
},
{
"path": "src/config/config.prod.ts",
"chars": 1278,
"preview": "import { IConfigOptions } from '../types'\n\n// 先从环境变量取配置\nlet config: IConfigOptions = {\n version: '2.9.0',\n serve: {\n "
},
{
"path": "src/config/index.ts",
"chars": 337,
"preview": "import { IConfigOptions } from \"../types\"\n\n// local or development or production\nlet configObj: IConfigOptions =\n (pr"
},
{
"path": "src/dispatch.ts",
"chars": 844,
"preview": "import * as cluster from 'cluster'\nimport * as path from 'path'\n\nlet now = () => new Date().toISOString().replace(/T/, '"
},
{
"path": "src/helpers/dedent.ts",
"chars": 1281,
"preview": "/**\n * copy from https://github.com/MartinKolarik/dedent-js/blob/master/src/index.ts\n * 解决多行字符串缩进时的空格问题\n */\n\nexport defa"
},
{
"path": "src/helpers/pandoc.ts",
"chars": 602,
"preview": "import { spawnSync } from 'child_process'\n\nexport default function pandoc(from: string, to: string, ...args: string[]) {"
},
{
"path": "src/models/bo/defaultVal.ts",
"chars": 502,
"preview": "import { Table, Column, Model, AutoIncrement, PrimaryKey, ForeignKey } from 'sequelize-typescript'\nimport { Repository }"
},
{
"path": "src/models/bo/historyLog.ts",
"chars": 1226,
"preview": "import { Table, Column, Model, AutoIncrement, PrimaryKey, DataType, AllowNull, ForeignKey, BelongsTo } from 'sequelize-t"
},
{
"path": "src/models/bo/interface.ts",
"chars": 2723,
"preview": "import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey, "
},
{
"path": "src/models/bo/logger.ts",
"chars": 1529,
"preview": "import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, BelongsTo, ForeignKey } from 'sequelize"
},
{
"path": "src/models/bo/module.ts",
"chars": 1971,
"preview": "import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey, "
},
{
"path": "src/models/bo/notification.ts",
"chars": 717,
"preview": "import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, Default } from 'sequelize-typescript'\n\n@T"
},
{
"path": "src/models/bo/organization.ts",
"chars": 1370,
"preview": "import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMan"
},
{
"path": "src/models/bo/organizationsMembers.ts",
"chars": 463,
"preview": "import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'\nimport { User, Organization } from "
},
{
"path": "src/models/bo/property.ts",
"chars": 2441,
"preview": "import { Table, Column, Model, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'se"
},
{
"path": "src/models/bo/repositoriesCollaborators.ts",
"chars": 479,
"preview": "import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'\nimport { Repository } from '../'\n\n@T"
},
{
"path": "src/models/bo/repositoriesMembers.ts",
"chars": 438,
"preview": "import { Table, Column, Model, ForeignKey, PrimaryKey } from 'sequelize-typescript'\nimport { Repository, User } from '.."
},
{
"path": "src/models/bo/repository.ts",
"chars": 2334,
"preview": "import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMan"
},
{
"path": "src/models/bo/user.ts",
"chars": 963,
"preview": "import { Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Unique, BelongsToMany } from 'se"
},
{
"path": "src/models/helper.ts",
"chars": 133,
"preview": "export let Helper: any = {\n include: [],\n exclude: {\n generalities: ['createdAt', 'updatedAt', 'deletedAt', 'reser"
},
{
"path": "src/models/index.ts",
"chars": 832,
"preview": "export { default as QueryInclude } from './util/queryInclude'\nexport { default as Interface } from './bo/interface'\nexpo"
},
{
"path": "src/models/sequelize.ts",
"chars": 1350,
"preview": "import { Sequelize } from 'sequelize-typescript'\nimport config from '../config'\nimport MigrateService from '../service/m"
},
{
"path": "src/models/util/helper.ts",
"chars": 233,
"preview": "declare interface IHelper {\n include: string[],\n exclude: {\n generalities: string[],\n }\n}\nexport let Helper: IHelp"
},
{
"path": "src/models/util/queryInclude.ts",
"chars": 3394,
"preview": "// TODO 2.2 如何缓存重复查询?https://github.com/rfink/sequelize-redis-cache\nimport { Helper } from './helper'\nimport User from '"
},
{
"path": "src/routes/account.ts",
"chars": 12459,
"preview": "import * as svgCaptcha from 'svg-captcha'\nimport { User, Notification, Logger, Organization, Repository } from '../model"
},
{
"path": "src/routes/analytics.ts",
"chars": 3356,
"preview": "import router from './router'\nimport Repository from \"../models/bo/repository\"\nimport Logger from \"../models/bo/logger\"\n"
},
{
"path": "src/routes/base.ts",
"chars": 384,
"preview": "import * as _ from 'lodash'\nimport { ParameterizedContext } from 'koa'\nconst inTestMode = process.env.TEST_MODE === 'tru"
},
{
"path": "src/routes/counter.ts",
"chars": 283,
"preview": "import router from './router'\nimport config from '../config'\n\nrouter.get('/app/counter', async(ctx) => {\n let app: any "
},
{
"path": "src/routes/export.ts",
"chars": 3098,
"preview": "import router from './router'\nimport { COMMON_ERROR_RES } from './utils/const'\nimport PostmanService from '../service/ex"
},
{
"path": "src/routes/index.ts",
"chars": 207,
"preview": "import router from './router'\n\nrequire('./counter')\nrequire('./account')\nrequire('./organization')\nrequire('./repository"
},
{
"path": "src/routes/migration.ts",
"chars": 1275,
"preview": "// import router from './router'\n// import migration from '../scripts/migration/migration'\n\n// // TODO 2.3 迁移期间采用单 worke"
},
{
"path": "src/routes/mock.ts",
"chars": 6556,
"preview": "import router from './router'\nimport { Repository, Interface, Property } from '../models'\nimport { QueryInclude } from '"
},
{
"path": "src/routes/organization.ts",
"chars": 7196,
"preview": "import router from './router'\nimport { Organization, User, Logger, Repository, Module, Interface, Property } from '../mo"
},
{
"path": "src/routes/repository.ts",
"chars": 34535,
"preview": "// TODO 2.1 大数据测试,含有大量模块、接口、属性的仓库\nimport router from './router'\nimport * as _ from 'underscore'\nimport Pagination from '"
},
{
"path": "src/routes/router.ts",
"chars": 801,
"preview": "import * as Router from 'koa-router'\nimport { DefaultState, DefaultContext } from \"koa\"\nimport config from '../config'\n\n"
},
{
"path": "src/routes/utils/access.ts",
"chars": 2082,
"preview": "import OrganizationService from '../../service/organization'\nimport RepositoryService from '../../service/repository'\nim"
},
{
"path": "src/routes/utils/const.ts",
"chars": 927,
"preview": "export enum COMMON_MSGS {\n ACCESS_DENY = '对不起,您没有访问该数据的权限。 Sorry, you have no access to visit this data.',\n}\n\nexport co"
},
{
"path": "src/routes/utils/helper.ts",
"chars": 5010,
"preview": "import { Module, Interface, Property } from '../../models'\nimport { Repository } from '../../models'\n\nconst genExampleMo"
},
{
"path": "src/routes/utils/pagination.ts",
"chars": 4847,
"preview": "/*\n Pagination\n\n Pure paging implementation reference.\n 纯粹的分页参考实现。\n\n 属性\n data 数据\n total"
},
{
"path": "src/routes/utils/tree.ts",
"chars": 12673,
"preview": "import { Property } from '../../models'\nimport * as _ from 'underscore'\nconst { VM } = require('vm2')\nimport * as Mock f"
},
{
"path": "src/routes/utils/url.ts",
"chars": 1209,
"preview": "let pathToRegexp = require('path-to-regexp')\n\nexport default class UrlUtils {\n\n public static getRelative = (url: strin"
},
{
"path": "src/scripts/app.ts",
"chars": 1836,
"preview": "// const debug = true\nimport * as Koa from 'koa'\nimport * as session from 'koa-generic-session'\nimport * as redisStore f"
},
{
"path": "src/scripts/dev.ts",
"chars": 574,
"preview": "import config from '../config'\nimport app from './app'\n\nconst start = () => {\n let execSync = require('child_process')."
},
{
"path": "src/scripts/init/bo.ts",
"chars": 2482,
"preview": "import { mock } from 'mockjs'\n\nconst scopes = ['request', 'response']\nconst methods = ['GET', 'POST', 'PUT', 'DELETE']\nc"
},
{
"path": "src/scripts/init/delos.ts",
"chars": 5353,
"preview": "import sequelize from '../../models/sequelize'\nimport { User, Organization, Repository, Module, Interface, Property } fr"
},
{
"path": "src/scripts/init/index.ts",
"chars": 286,
"preview": "import { init, after } from './delos'\n/**\n * initialize database\n */\nexport async function main () {\n await init()\n co"
},
{
"path": "src/scripts/initSchema.ts",
"chars": 263,
"preview": "import sequelize from \"../models/sequelize\"\n\nsequelize\n .sync({\n force: true,\n })\n .then(() => {\n console.log(\""
},
{
"path": "src/scripts/openChrome.applescript",
"chars": 2337,
"preview": "(*\nCopyright (c) 2015-present, Facebook, Inc.\nAll rights reserved.\n\nThis source code is licensed under the BSD-style lic"
},
{
"path": "src/scripts/rap2_delos.sql",
"chars": 14091,
"preview": "-- MySQL dump 10.13 Distrib 5.7.12, for osx10.9 (x86_64)\n--\n-- Host: localhost Database: RAP2_DELOS_APP_LOCAL\n-- ---"
},
{
"path": "src/scripts/setToken.ts",
"chars": 1026,
"preview": "// 补数据 token\nimport nanoid = require(\"nanoid\")\nimport sequelize from \"../models/sequelize\"\nimport { Repository } from \"."
},
{
"path": "src/scripts/updateSchema.ts",
"chars": 260,
"preview": "import sequelize from \"../models/sequelize\"\n\nsequelize\n .sync({\n alter: true\n })\n .then(() => {\n console.log(\"成"
},
{
"path": "src/scripts/worker.ts",
"chars": 753,
"preview": "import config from '../config'\nimport app from './app'\nconst start = () => {\n // https://github.com/node-modules/gracef"
},
{
"path": "src/service/export/docx.ts",
"chars": 410,
"preview": "import MarkdownService from './markdown'\nimport pandoc from '../../helpers/pandoc'\n\nconst markdownToDocx = pandoc('markd"
},
{
"path": "src/service/export/markdown.ts",
"chars": 2545,
"preview": "import { Repository, Interface, Module, Property } from '../../models'\nimport dedent from '../../helpers/dedent'\nimport "
},
{
"path": "src/service/export/pdf.ts",
"chars": 695,
"preview": "// import MarkdownService from './markdown'\n// import markdownpdf = require('markdown-pdf')\n// // import { Readable, Str"
},
{
"path": "src/service/export/postman.ts",
"chars": 3357,
"preview": "import { PostmanCollection, Folder, Item } from \"../../types/postman\"\nimport { Repository, Interface, Module, Property }"
},
{
"path": "src/service/mail.ts",
"chars": 11380,
"preview": "import * as nodemailer from 'nodemailer'\nimport config from '../config'\n\nexport default class MailService {\n public sta"
},
{
"path": "src/service/migrate.ts",
"chars": 46274,
"preview": "import { Repository, Module, Interface, Property, User, QueryInclude } from '../models'\nimport { SCOPES } from '../model"
},
{
"path": "src/service/mock.ts",
"chars": 7530,
"preview": "import { Repository, Interface, Property, DefaultVal } from '../models'\nimport { Op } from 'sequelize'\nimport urlUtils f"
},
{
"path": "src/service/organization.ts",
"chars": 2236,
"preview": "import seq from '../models/sequelize'\nimport Pagination from '../routes/utils/pagination'\nimport Utils from './utils'\nex"
},
{
"path": "src/service/redis.ts",
"chars": 2010,
"preview": "import * as redis from 'redis'\nimport config from '../config'\nimport * as ioredis from 'ioredis'\nimport { THEME_TEMPLATE"
},
{
"path": "src/service/repository.ts",
"chars": 6407,
"preview": "import { Repository, RepositoriesMembers, Interface, Property, Module, HistoryLog, User } from '../models'\nimport { Move"
},
{
"path": "src/service/task.ts",
"chars": 672,
"preview": "import * as schedule from 'node-schedule'\nimport { Interface } from '../models'\nimport { Op } from 'sequelize'\nimport { "
},
{
"path": "src/service/utils.ts",
"chars": 268,
"preview": "export default class Utils {\n public static escapeSQL(str: string) {\n if (typeof str === 'string') {\n str = str"
},
{
"path": "src/types/custom-typings.d.ts",
"chars": 24,
"preview": "declare module 'ioredis'"
},
{
"path": "src/types/index.d.ts",
"chars": 760,
"preview": "/// <reference path=\"custom-typings.d.ts\" />\nimport { PoolOptions } from \"sequelize\"\nimport { ISequelizeConfig } from \"s"
},
{
"path": "src/types/postman.d.ts",
"chars": 16031,
"preview": "/**\n * This file was automatically generated by json-schema-to-typescript.\n * DO NOT MODIFY IT BY HAND. Instead, modify "
},
{
"path": "test/helper.js",
"chars": 2757,
"preview": "/* global before, after */\nconst Random = require('mockjs').Random\nmodule.exports = {\n mockUsers: () => [{}, {}, {}, {}"
},
{
"path": "test/index.js",
"chars": 103,
"preview": "// clear console\nprocess.stdout.write(process.platform === 'win32' ? '\\x1Bc' : '\\x1B[2J\\x1B[3J\\x1B[H')\n"
},
{
"path": "test/test.account.js",
"chars": 3033,
"preview": "/* global describe, it */\nconst app = require('../dist/scripts/app').default\nconst request = require('supertest').agent("
},
{
"path": "test/test.counter.js",
"chars": 706,
"preview": "/* global describe, it */\nconst app = require('../dist/scripts/app').default\nconst request = require('supertest').agent("
},
{
"path": "test/test.interface.js",
"chars": 3581,
"preview": "/* global describe, it, before */\nlet app = require('../dist/scripts/app').default\nlet request = require('supertest').ag"
},
{
"path": "test/test.js",
"chars": 876,
"preview": "/* global describe, it */\nlet app = require('../dist/scripts/app').default\nlet request = require('supertest').agent(app."
},
{
"path": "test/test.mock.js",
"chars": 2754,
"preview": "/* global describe, it, before */\nlet app = require('../dist/scripts/app').default\nlet request = require('supertest').ag"
},
{
"path": "test/test.module.js",
"chars": 2754,
"preview": "/* global describe, it, before */\nlet app = require('../dist/scripts/app').default\nlet request = require('supertest').ag"
},
{
"path": "test/test.organization.js",
"chars": 4214,
"preview": "/* global describe, it, before */\nconst app = require('../dist/scripts/app').default\nconst request = require('supertest'"
},
{
"path": "test/test.property.js",
"chars": 3165,
"preview": "/* global describe, it, before */\nlet app = require('../dist/scripts/app').default\nlet request = require('supertest').ag"
},
{
"path": "test/test.repository.js",
"chars": 4411,
"preview": "/* global describe, it, before */\nconst app = require('../dist/scripts/app').default\nconst request = require('supertest'"
},
{
"path": "tsconfig.json",
"chars": 701,
"preview": "{\n \"compilerOptions\": {\n \"outDir\": \"./dist\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"experime"
},
{
"path": "tslint.json",
"chars": 1382,
"preview": "{\n \"tslint.enable\": true,\n \"rules\": {\n \"class-name\": true,\n \"comment-format\": [\n true,\n \"check-space\"\n"
}
]
About this extraction
This page contains the full source code of the thx/rap2-delos GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 122 files (313.3 KB), approximately 89.0k tokens, and a symbol index with 212 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.