Merge branch 'dev' of gitlab.corp.qunar.com:mfe/yapi into dev

This commit is contained in:
wenbo.dong 2017-07-27 14:35:29 +08:00
commit 2c2d9b47e7
26 changed files with 307 additions and 326 deletions

View File

@ -43,9 +43,9 @@ class FootItem extends Component {
render () { render () {
return ( return (
<div className = 'footItem'> <div className = 'footItem'>
<h4><Icon type= { this.props.iconType } style={{ fontSize: 16 }} /> { this.props.title } </h4> <h4><Icon type= { this.props.iconType } style={{ fontSize: 16 }} />&nbsp;&nbsp; { this.props.title } </h4>
{ this.props.linkList.map(function(item,i){ { this.props.linkList.map(function(item,i){
return (<div key = {i}><a href = { item.itemLink }><span>{ item.itemTitle }</span></a></div>); return (<div key = {i}>&nbsp;&nbsp;<a href = { item.itemLink }><span>{ item.itemTitle }</span></a></div>);
}) } }) }
</div> </div>
); );

View File

@ -2,7 +2,7 @@
@import '../../styles/mixin.scss'; @import '../../styles/mixin.scss';
.footer{ .footer{
// max-width: 12rem; @include wrap-width-limit;
margin: 0px auto; margin: 0px auto;
clear: both; clear: both;
font-size: 12px; font-size: 12px;
@ -48,7 +48,7 @@
} }
a{ a{
font-weight: 200; font-weight: 200;
color: #108ee9; color: #b3bdc1;
&:hover{ &:hover{
color: white; color: white;
} }

View File

@ -146,15 +146,19 @@ export default class HeaderCom extends Component {
} }
render () { render () {
const { login, user, msg, uid, curKey } = this.props; const { login, user, msg, uid, curKey } = this.props;
const headerStyle = { const headerImgStyle = login?{}:{
'background': 'url(./image/header-bg-img.jpg) no-repeat', 'background': 'url(./image/header-bg-img.jpg) no-repeat',
'backgroundSize':'100% 100%' 'backgroundSize':'100% 100%'
} };
const headerShadeStyle = login? {
'padding':'0'
}: {
'background': 'linear-gradient(to bottom,rgba(0,0,0,0.6),rgba(0,0,0,0.5))',
'padding':'0'
};
return ( return (
<acticle className={`header-box`} style={headerStyle}> <acticle className={`header-box`} style={headerImgStyle}>
<Header style={{ <Header style={headerShadeStyle}>
background: "linear-gradient(to bottom,rgba(0,0,0,0.6),rgba(0,0,0,0.5))"
}}>
<div className="content"> <div className="content">
<div className="logo"> <div className="logo">
<Link to="/" onClick={this.relieveLink}>YAPI</Link> <Link to="/" onClick={this.relieveLink}>YAPI</Link>

View File

@ -7,20 +7,11 @@ $color-blue-deeper: #34495E;
$color-grey-deep : #929aac; $color-grey-deep : #929aac;
$color-black-light : #404040; $color-black-light : #404040;
/* .header-box.css */ /* .header-box.css */
//.light{
// background-color: #f7fafc;
// color: $color-blue;
//}
//.dark {
// background-color: $color-black-light;
// color: $color-white;
//}
.header-box { .header-box {
display: block; display: block;
font-size: 0.14rem; font-size: 0.14rem;
z-index: 99; z-index: 99;
// 内容宽度 @include wrap-width-limit;
.content { .content {
@include row-width-limit; @include row-width-limit;
margin: 0 auto; margin: 0 auto;

View File

@ -52,6 +52,8 @@ $color-white: #fff;
} }
} }
.img-container{ .img-container{
height: 100%;
width: 100%;
padding-right: .15rem; padding-right: .15rem;
//background-image: url("#{$imgUrl}demo-img.png"); //background-image: url("#{$imgUrl}demo-img.png");
img{ img{

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Button, Input, Select, Card } from 'antd' import { Button, Input, Select, Card, Alert } from 'antd'
import { autobind } from 'core-decorators'; import { autobind } from 'core-decorators';
import crossRequest from 'cross-request'; import crossRequest from 'cross-request';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
@ -167,6 +167,10 @@ export default class InterfaceTest extends Component {
this.setState({ params }); this.setState({ params });
} }
hasCrossRequestPlugin() {
const dom = document.getElementById('y-request');
return dom.getAttribute('key') === 'yapi';
}
render () { render () {
@ -175,10 +179,29 @@ export default class InterfaceTest extends Component {
const search = URL.format({ const search = URL.format({
query query
}); });
const hasPlugin = this.hasCrossRequestPlugin();
return ( return (
<div className="interface-test"> <div className="interface-test">
<div style={{padding: '0 20%'}}>
{ hasPlugin ? '' :
<Alert
message={
<div>
温馨提示当前正在使用接口测试服务请安装我们为您免费提供的&nbsp;
<a
target="blank"
href="https://chrome.google.com/webstore/detail/cross-request/cmnlfmgbjmaciiopcgodlhpiklaghbok?hl=en-US"
>
测试增强插件 [点击获取]
</a>
</div>
}
type="warning"
/>
}
</div>
<div className="interface-name">{interfaceName}</div> <div className="interface-name">{interfaceName}</div>
<div className="req-part"> <div className="req-part">
<div className="req-row href"> <div className="req-row href">

View File

@ -12,9 +12,10 @@ $color-black-lighter: #404040;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
.main-one{ .main-one{
@include wrap-width-limit;
.home-des{ .home-des{
color: $color-blue-grey-lighter; color: $color-blue-grey-lighter;
padding: .5rem 0 0; padding: .4rem 0 0;
.title{ .title{
font-size: .6rem; font-size: .6rem;
} }
@ -26,7 +27,7 @@ $color-black-lighter: #404040;
color: $color-white; color: $color-white;
} }
.img-container{ .img-container{
margin-bottom: -.2rem; margin-bottom: -.3rem;
img{ img{
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -75,6 +76,7 @@ $color-black-lighter: #404040;
.main-part{ .main-part{
padding: .9rem .5rem; padding: .9rem .5rem;
height: 5.8rem; height: 5.8rem;
@include wrap-width-limit;
&:nth-child(odd){ &:nth-child(odd){
background-color: $color-blue-lighter; background-color: $color-blue-lighter;
} }
@ -83,6 +85,7 @@ $color-black-lighter: #404040;
} }
} }
.feat-part{ .feat-part{
@include wrap-width-limit;
padding: .9rem .5rem; padding: .9rem .5rem;
background-color: $color-white; background-color: $color-white;
p{ p{

View File

@ -37,14 +37,15 @@
} }
.group-operate { .group-operate {
height: 48px; height: 48px;
min-width: 263px;
padding: 10px 6px; padding: 10px 6px;
background: #eee; background: #eee;
display: flex;
justify-content: space-around;
.search { .search {
display: inline-block;
margin-right: 6px; margin-right: 6px;
width: 162px; flex-grow: 1;
} }
} }
.group-list { .group-list {
max-height: 650px; max-height: 650px;

View File

@ -138,8 +138,8 @@ class Profile extends Component {
userNameEditHtml = <div> userNameEditHtml = <div>
<Input value={_userinfo.username} name="username" onChange={this.changeUserinfo} placeholder="用户名" /> <Input value={_userinfo.username} name="username" onChange={this.changeUserinfo} placeholder="用户名" />
<ButtonGroup className="edit-buttons" > <ButtonGroup className="edit-buttons" >
<Button className="edit-button" onClick={() => { this.handleEdit('usernameEdit', false) }} >Cancel</Button> <Button size={'small'} className="edit-button" onClick={() => { this.handleEdit('usernameEdit', false) }} >Cancel</Button>
<Button className="edit-button" onClick={ () => { this.updateUserinfo('username')} } type="primary">OK</Button> <Button size={'small'} className="edit-button" onClick={ () => { this.updateUserinfo('username')} } type="primary">OK</Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
} }
@ -154,8 +154,8 @@ class Profile extends Component {
emailEditHtml = <div> emailEditHtml = <div>
<Input placeholder="Email" value={_userinfo.email} name="email" onChange={this.changeUserinfo} /> <Input placeholder="Email" value={_userinfo.email} name="email" onChange={this.changeUserinfo} />
<ButtonGroup className="edit-buttons" > <ButtonGroup className="edit-buttons" >
<Button className="edit-button" onClick={() => { this.handleEdit('emailEdit', false) }} >Cancel</Button> <Button size={'small'} className="edit-button" onClick={() => { this.handleEdit('emailEdit', false) }} >Cancel</Button>
<Button className="edit-button" type="primary" onClick={ () => { this.updateUserinfo('email')} }>OK</Button> <Button size={'small'} className="edit-button" type="primary" onClick={ () => { this.updateUserinfo('email')} }>OK</Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
} }
@ -175,15 +175,15 @@ class Profile extends Component {
} }
if (this.state.secureEdit === false) { if (this.state.secureEdit === false) {
secureEditHtml = <Button type="primary" onClick={() => { this.handleEdit('secureEdit', true) }}>密码修改</Button> secureEditHtml = <Button size={'small'} icon="edit" onClick={() => { this.handleEdit('secureEdit', true) }}>修改</Button>
} else { } else {
secureEditHtml = <div> secureEditHtml = <div>
<Input style={{display: this.state.userinfo.role === 'admin' ? 'none': ''}} placeholder="旧的密码" type="password" name="old_password" id="old_password" /> <Input style={{display: this.state.userinfo.role === 'admin' ? 'none': ''}} placeholder="旧的密码" type="password" name="old_password" id="old_password" />
<Input placeholder="新的密码" type="password" name="password" id="password" /> <Input placeholder="新的密码" type="password" name="password" id="password" />
<Input placeholder="确认密码" type="password" name="verify_pass" id="verify_pass" /> <Input placeholder="确认密码" type="password" name="verify_pass" id="verify_pass" />
<ButtonGroup className="edit-buttons" > <ButtonGroup className="edit-buttons" >
<Button className="edit-button" onClick={() => { this.handleEdit('secureEdit', false) }}>Cancel</Button> <Button size={'small'} className="edit-button" onClick={() => { this.handleEdit('secureEdit', false) }}>Cancel</Button>
<Button className="edit-button" onClick={this.updatePassword} type="primary">OK</Button> <Button size={'small'} className="edit-button" onClick={this.updatePassword} type="primary">OK</Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
} }
@ -228,7 +228,7 @@ class Profile extends Component {
</Row> </Row>
<Row className="user-item" type="flex" justify="start"> <Row className="user-item" type="flex" justify="start">
<Col span={4}>安全</Col> <Col span={4}>密码</Col>
<Col span={12}> <Col span={12}>
{secureEditHtml} {secureEditHtml}
</Col> </Col>

View File

@ -24,15 +24,22 @@
border-radius:5px; border-radius:5px;
margin-top: 15px; margin-top: 15px;
.search{ .search{
margin: 5px; padding: 5px;
background-color: #eee;
} }
ul{border:none} ul{border:none}
.ant-menu-item{
font-size: 14px;
}
} }
.user-name{ .user-name{
padding: 10px 0px; padding: 24px 0px;
text-align: center; text-align: center;
background-color: #34495e; background-color: #34495e;
color: white; color: white;
font-size: 16px;
border-top-left-radius:5px;
border-top-right-radius: 5px;
span{ span{
margin-right: 10px; margin-right: 10px;
} }
@ -62,17 +69,27 @@
-webkit-box-flex: 1; -webkit-box-flex: 1;
margin-top: 15px; margin-top: 15px;
margin-left: 15px; margin-left: 15px;
padding: 10px 30px; padding: 24px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20); box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
background: #FFF; background: #FFF;
border-radius:5px; border-radius:5px;
.ant-btn-group{
margin-top: 12px;
}
.user-item { .user-item {
min-height:35px; min-height:35px;
line-height:35px; line-height:35px;
margin: 5px; margin: 5px;
margin-left: 0px;
margin-bottom:10px; margin-bottom:10px;
border-bottom: 1px solid #f1f3f6; border-bottom: 1px solid #f1f3f6;
padding-bottom: 10px; padding-bottom: 10px;
#old_password,#password,#verify_pass{
margin-top: 20px;
}
#old_password{
margin-top: 0px;
}
.ant-col-4{ .ant-col-4{
color: black; color: black;
padding: 0px 10px; padding: 0px 10px;
@ -90,13 +107,13 @@
cursor: pointer cursor: pointer
} }
.edit-buttons{ // .edit-buttons{
margin:10px; // margin:10px;
} // }
.edit-button{ // .edit-button{
margin: 5px; // margin: 5px;
} // }
} }
} }

View File

@ -2,3 +2,7 @@
max-width: 11.7rem; max-width: 11.7rem;
min-width: 9.7rem; min-width: 9.7rem;
} }
@mixin wrap-width-limit {
min-width: 10.7rem;
}

21
config_example.json Normal file
View File

@ -0,0 +1,21 @@
{
"port": "3000",
"webhost": "yapi.local.qunar.com",
"adminAccount": "admin@admin.com",
"db": {
"servername": "127.0.0.1",
"DATABASE": "yapi",
"port": 27017,
"user": "test1",
"pass": "test1"
},
"mail": {
"host": "smtp.163.com",
"port": 465,
"from": "***@163.com",
"auth": {
"user": "***@163.com",
"pass": "*****"
}
}
}

View File

@ -1,35 +0,0 @@
### 1.User
- /user/get //获取用户个人信息
- /user/list //获取用户列表,需要提供分页功能
- /user/del //删除用户
- /user/up //更新用户个人信息
- /uesr/login //登录
- /user/reg //注册
- /user/login/status //获取用户登录状态
### 2.Group
- /group/list //获取项目分组列表
- /group/add //添加
- /group/up //更新
- /group/del //删除
### 3.Project
- /project/list/:group_id
- /project/add //添加项目
- /project/up //编辑项目
- /project/del //删除项目
- /project/add_member //添加项目成员
- /project/del_member //删除项目成员
- /project/get //获取一个项目详细信息
### 4.interface
- /interface/list/:project_id
- /interface/add
- /interface/up
- /interface/del
- /interface/mock //执行用户定义的mock返回mock结果
### 5. mock服务器
用户修改host指到yapi服务器yapi服务器收到请求后根据domain/basepath 找到相对应的项目根据req信息找到对应的接口执行用户定义的mock数据返回给用户相应的结果

View File

@ -1,93 +0,0 @@
### 数据库字典
### 数据库基于mongodb
#### 1.User数据表表名:user
```json
{
_id: (int)
username: (string)
password: (sha1)
passsalt: (string)
email : (string)
role : (string)
add_time: (int)
up_time: (int)
}
````
#### 2.Project 数据表,表名:project
```json
{
_id: (int)
uid : (int)
name: (string)
basepath: (string)
desc: (string)
group_id: (int)
members: [
... //成员uid
]
prd_host: (string)//网站上线的domain,可用来获取mock数据
env:(object){
'local环境' : 'http://127.0.0.1'
}
add_time: (int)
up_time: (int)
}
````
#### 3.api 数据表,表名:interface
```json
{
_id: (int)
uid: (int) //负责人uid
path: (string)
group_id: (int)
status: (int)
desc : (string)
add_time: (int)
up_time : (int)
req_headers:(Object){
"header_name":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
req_params_type: (form|raw)
req_params: (Object){
"key" : (Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
res_header: (Object){
"header_name":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
res_body_type: (text|json),
res_body: (Object){
"key":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
}
```
#### 4.项目分组,表名: group
```json
{
_id: (int),
uid: (int),
group_name: (string),
group_desc: (string),
add_time: (int),
up_time: (int)
}
```

View File

@ -12,14 +12,14 @@ const SRC = 'server/**/*.js';
function generateBabel(status) { function generateBabel(status) {
const babelProcess = babel({ const babelProcess = babel({
presets: ['es2015', "stage-3"], presets: ['es2015', 'stage-3'],
plugins: ['transform-runtime'] plugins: ['transform-runtime']
}); });
babelProcess.on('error', function (e) { babelProcess.on('error', function (e) {
const restart = status ? status.count < 2 : true; const restart = status ? status.count < 2 : true;
console.error(e); console.error(e); // eslint-disable-line
output('error', 'babel 编译失败!', restart); output('error', 'babel 编译失败!', restart);
if (status) { if (status) {
@ -39,7 +39,13 @@ function excuteCmd(cmd, args, opts) {
}); });
command.stderr.on('data', data => { command.stderr.on('data', data => {
output('log', `${NAME} ${data.toString()}`, true); const message = data.toString();
output('log', `${NAME} ${message}`, true);
if (~message.indexOf('building complete')) {
waitingSpinner();
}
}); });
return command; return command;
@ -50,12 +56,12 @@ function output(type, message, restart = false) {
if (type === 'success') { if (type === 'success') {
message = '✔ ' + message; message = '✔ ' + message;
console.log(chalk.green(message)); console.log(chalk.green(message)); // eslint-disable-line
} else if (type === 'error') { } else if (type === 'error') {
message = '✖ ' + message; message = '✖ ' + message;
console.log(chalk.red(message)); console.log(chalk.red(message)); // eslint-disable-line
} else { } else {
console.log(message); console.log(message); // eslint-disable-line
} }
if (restart) { if (restart) {
spinner.start(); spinner.start();
@ -63,6 +69,7 @@ function output(type, message, restart = false) {
} }
function waitingSpinner() { function waitingSpinner() {
spinner.stop();
spinner = ora({ spinner = ora({
text: '等待文件变更...', text: '等待文件变更...',
spinner: 'circleQuarters', spinner: 'circleQuarters',
@ -71,7 +78,7 @@ function waitingSpinner() {
} }
gulp.task('removeDist', [], function () { gulp.task('removeDist', [], function () {
return fs.removeSync(DIST) return fs.removeSync(DIST);
}); });
gulp.task('initialBuild', ['removeDist'], () => { gulp.task('initialBuild', ['removeDist'], () => {
@ -95,9 +102,14 @@ gulp.task('initialBuild', ['removeDist'], () => {
}); });
gulp.task('default', ['initialBuild'], () => { gulp.task('default', ['initialBuild'], () => {
gulp.watch(SRC, (event) => { gulp.watch('client/**/*', event => {
let originFilePath = path.relative(path.join(__dirname, 'server'), event.path) spinner.stop();
let distPath = path.resolve(DIST, path.join(originFilePath)) spinner = ora(`正在编译 ${event.path}`).start();
});
gulp.watch(SRC, event => {
let originFilePath = path.relative(path.join(__dirname, 'server'), event.path);
let distPath = path.resolve(DIST, path.join(originFilePath));
spinner.text = `正在编译 ${event.path}...`; spinner.text = `正在编译 ${event.path}...`;
gulp.src(event.path).pipe(generateBabel()) gulp.src(event.path).pipe(generateBabel())

View File

@ -1,18 +0,0 @@
module.exports = {
'port': '3000',
'webhost': 'yapi.local.qunar.com',
'adminAccount': 'admin@admin.com',
'db': {
'servername': '127.0.0.1',
'DATABASE': 'yapi',
'port': 27017
},
'mail': {
'host': 'smtp.mail.com',
'port': 4652,
'auth': {
'user': '****@mail.com',
'pass': '**********'
}
}
};

View File

@ -1,20 +1,19 @@
import yapi from '../yapi.js' import yapi from '../yapi.js';
import projectModel from '../models/project.js' import projectModel from '../models/project.js';
import userModel from '../models/user.js' import userModel from '../models/user.js';
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
class baseController {
class baseController{ constructor(ctx) {
constructor(ctx){
this.ctx = ctx; this.ctx = ctx;
//网站上线后role对象key是不能修改的value可以修改 //网站上线后role对象key是不能修改的value可以修改
this.roles = { this.roles = {
admin: 'Admin', admin: 'Admin',
member: '网站会员' member: '网站会员'
} };
} }
async init(ctx){ async init(ctx) {
this.$user = null; this.$user = null;
let ignoreRouter = [ let ignoreRouter = [
'/user/login_by_token', '/user/login_by_token',
@ -22,73 +21,93 @@ class baseController{
'/user/reg', '/user/reg',
'/user/status', '/user/status',
'/user/logout' '/user/logout'
] ];
if(ignoreRouter.indexOf(ctx.path) > -1){ if (ignoreRouter.indexOf(ctx.path) > -1) {
this.$auth = true; this.$auth = true;
}else{ } else {
await this.checkLogin(ctx) await this.checkLogin(ctx);
} }
} }
getUid(ctx){ getUid() {
return parseInt(this.$uid, 10); return parseInt(this.$uid, 10);
} }
async checkLogin(ctx){ async checkLogin(ctx) {
let token = ctx.cookies.get('_yapi_token'); let token = ctx.cookies.get('_yapi_token');
let uid = ctx.cookies.get('_yapi_uid'); let uid = ctx.cookies.get('_yapi_uid');
try{
if(!token || !uid) return false; try {
if (!token || !uid) return false;
let userInst = yapi.getInst(userModel); //创建user实体 let userInst = yapi.getInst(userModel); //创建user实体
let result = await userInst.findById(uid); let result = await userInst.findById(uid);
let decoded = jwt.verify(token, result.passsalt) let decoded = jwt.verify(token, result.passsalt);
if(decoded.uid == uid){
if (decoded.uid == uid) {
this.$uid = uid; this.$uid = uid;
this.$auth = true; this.$auth = true;
this.$user = result; this.$user = result;
return true; return true;
} }
return false; return false;
}catch(e){ } catch (e) {
return false; return false;
} }
} }
async getLoginStatus(ctx){ async getLoginStatus(ctx) {
if(await this.checkLogin(ctx) === true){ if (await this.checkLogin(ctx) === true) {
return ctx.body = yapi.commons.resReturn(yapi.commons.fieldSelect(this.$user,['_id','username','email', 'up_time', 'add_time'])); return ctx.body = yapi.commons.resReturn(yapi.commons.fieldSelect(this.$user, ['_id', 'username', 'email', 'up_time', 'add_time']));
} }
return ctx.body = yapi.commons.resReturn(null, 300 , 'Please login.'); return ctx.body = yapi.commons.resReturn(null, 300, 'Please login.');
} }
getRole(){ getRole() {
return this.$user.role; return this.$user.role;
} }
async jungeProjectAuth(id){ async jungeProjectAuth(id) {
let model = yapi.getInst(projectModel); let model = yapi.getInst(projectModel);
if(this.getRole() === 'admin') return true;
if(!id) return false; if (this.getRole() === 'admin') {
let result = await model.get(id);
if(result.uid === this.getUid()){
return true; return true;
} }
if (!id) {
return false;
}
let result = await model.get(id);
if (result.uid === this.getUid()) {
return true;
}
return false; return false;
} }
async jungeMemberAuth(id, member_uid){ async jungeMemberAuth(id, member_uid) {
let model = yapi.getInst(projectModel); let model = yapi.getInst(projectModel);
if(this.getRole() === 'admin') return true;
if(!id || !member_uid) return false; if (this.getRole() === 'admin') {
let result = await model.checkMemberRepeat(id, member_uid);
if(result > 0){
return true; return true;
} }
if (!id || !member_uid) {
return false;
}
let result = await model.checkMemberRepeat(id, member_uid);
if (result > 0) {
return true;
}
return false; return false;
} }
} }
module.exports = baseController module.exports = baseController;

View File

@ -1,14 +1,12 @@
import groupModel from '../models/group.js' import groupModel from '../models/group.js';
import yapi from '../yapi.js' import yapi from '../yapi.js';
import baseController from './base.js' import baseController from './base.js';
import projectModel from '../models/project.js' import projectModel from '../models/project.js';
// class groupController extends baseController {
class groupController extends baseController{ constructor(ctx) {
constructor(ctx){ super(ctx);
super(ctx)
} }
/** /**
* 添加项目分组 * 添加项目分组
@ -23,37 +21,45 @@ class groupController extends baseController{
*/ */
async add(ctx) { async add(ctx) {
let params = ctx.request.body; let params = ctx.request.body;
params = yapi.commons.handleParams(params, { params = yapi.commons.handleParams(params, {
group_name: 'string', group_name: 'string',
group_desc: 'string' group_desc: 'string'
}) });
if(this.getRole() !== 'admin'){
return ctx.body = yapi.commons.resReturn(null,401,'没有权限'); if (this.getRole() !== 'admin') {
return ctx.body = yapi.commons.resReturn(null, 401, '没有权限');
} }
if(!params.group_name){
if (!params.group_name) {
return ctx.body = yapi.commons.resReturn(null, 400, '项目分组名不能为空'); return ctx.body = yapi.commons.resReturn(null, 400, '项目分组名不能为空');
} }
var groupInst = yapi.getInst(groupModel);
let groupInst = yapi.getInst(groupModel);
var checkRepeat = await groupInst.checkRepeat(params.group_name);
if(checkRepeat > 0){ let checkRepeat = await groupInst.checkRepeat(params.group_name);
return ctx.body = yapi.commons.resReturn(null, 401, '项目分组名已存在');
if (checkRepeat > 0) {
return ctx.body = yapi.commons.resReturn(null, 401, '项目分组名已存在');
} }
let data = { let data = {
group_name: params.group_name, group_name: params.group_name,
group_desc: params.group_desc, group_desc: params.group_desc,
uid: this.getUid(), uid: this.getUid(),
add_time: yapi.commons.time(), add_time: yapi.commons.time(),
up_time: yapi.commons.time() up_time: yapi.commons.time()
} };
try{
try {
let result = await groupInst.save(data); let result = await groupInst.save(data);
result = yapi.commons.fieldSelect(result, ['_id', 'group_name', 'group_desc', 'uid']); result = yapi.commons.fieldSelect(result, ['_id', 'group_name', 'group_desc', 'uid']);
ctx.body = yapi.commons.resReturn(result); ctx.body = yapi.commons.resReturn(result);
}catch(e){ } catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message) ctx.body = yapi.commons.resReturn(null, 402, e.message);
} }
} }
/** /**
@ -65,14 +71,13 @@ class groupController extends baseController{
* @returns {Object} * @returns {Object}
* @example ./api/group/list.json * @example ./api/group/list.json
*/ */
async list(ctx) { async list(ctx) {
try{ try {
var groupInst = yapi.getInst(groupModel); var groupInst = yapi.getInst(groupModel);
let result = await groupInst.list(); let result = await groupInst.list();
ctx.body = yapi.commons.resReturn(result) ctx.body = yapi.commons.resReturn(result);
}catch(e){ } catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message) ctx.body = yapi.commons.resReturn(null, 402, e.message);
} }
} }
@ -86,27 +91,30 @@ class groupController extends baseController{
* @returns {Object} * @returns {Object}
* @example ./api/group/del.json * @example ./api/group/del.json
*/ */
async del(ctx) {
if (this.getRole() !== 'admin') {
return ctx.body = yapi.commons.resReturn(null, 401, '没有权限');
}
async del(ctx){ try {
if(this.getRole() !== 'admin'){ let groupInst = yapi.getInst(groupModel);
return ctx.body = yapi.commons.resReturn(null,401,'没有权限'); let projectInst = yapi.getInst(projectModel);
} let id = ctx.request.body.id;
try{
var groupInst = yapi.getInst(groupModel); if (!id) {
var projectInst = yapi.getInst(projectModel);
let id = ctx.request.body.id;
if(!id){
return ctx.body = yapi.commons.resReturn(null, 402, 'id不能为空'); return ctx.body = yapi.commons.resReturn(null, 402, 'id不能为空');
} }
let count = await projectInst.countByGroupId(id);
if(count > 0){ let count = await projectInst.countByGroupId(id);
if (count > 0) {
return ctx.body = yapi.commons.resReturn(null, 403, '请先删除该分组下的项目'); return ctx.body = yapi.commons.resReturn(null, 403, '请先删除该分组下的项目');
} }
let result = await groupInst.del(id); let result = await groupInst.del(id);
ctx.body = yapi.commons.resReturn(result) ctx.body = yapi.commons.resReturn(result);
}catch(err){ } catch (err) {
ctx.body = yapi.commons.resReturn(null, 402, e.message) ctx.body = yapi.commons.resReturn(null, 402, err.message);
} }
} }
@ -122,33 +130,32 @@ class groupController extends baseController{
* @returns {Object} * @returns {Object}
* @example ./api/group/up.json * @example ./api/group/up.json
*/ */
async up(ctx) {
async up(ctx){ if (this.getRole() !== 'admin') {
if(this.getRole() !== 'admin'){ return ctx.body = yapi.commons.resReturn(null, 401, '没有权限');
return ctx.body = yapi.commons.resReturn(null,401,'没有权限');
} }
try{
try {
ctx.request.body = yapi.commons.handleParams(ctx.request.body, { ctx.request.body = yapi.commons.handleParams(ctx.request.body, {
id: 'number', id: 'number',
group_name: 'string', group_name: 'string',
group_desc: 'string' group_desc: 'string'
}) });
var groupInst = yapi.getInst(groupModel); let groupInst = yapi.getInst(groupModel);
let id = ctx.request.body.id; let id = ctx.request.body.id;
let data = {}; let data = {};
ctx.request.body.group_name && (data.group_name = ctx.request.body.group_name) ctx.request.body.group_name && (data.group_name = ctx.request.body.group_name);
ctx.request.body.group_desc && (data.group_desc = ctx.request.body.group_desc) ctx.request.body.group_desc && (data.group_desc = ctx.request.body.group_desc);
if(Object.keys(data).length === 0 ){ if (Object.keys(data).length === 0) {
ctx.body = yapi.commons.resReturn(null, 404, '分组名和分组描述不能为空'); ctx.body = yapi.commons.resReturn(null, 404, '分组名和分组描述不能为空');
} }
let result = await groupInst.up(id, data); let result = await groupInst.up(id, data);
ctx.body = yapi.commons.resReturn(result) ctx.body = yapi.commons.resReturn(result);
}catch(err){ } catch (err) {
ctx.body = yapi.commons.resReturn(null, 402, e.message) ctx.body = yapi.commons.resReturn(null, 402, err.message);
} }
} }
} }
module.exports = groupController;
module.exports = groupController

View File

@ -115,7 +115,7 @@ exports.sendMail = (options, cb) => {
try { try {
yapi.mail.sendMail({ yapi.mail.sendMail({
from: yapi.WEBCONFIG.mail.auth.user, from: yapi.WEBCONFIG.mail.from,
to: options.to, to: options.to,
subject: 'yapi平台', subject: 'yapi平台',
html: options.contents html: options.contents

View File

@ -2,7 +2,10 @@ import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import config from '../config.js'; import config from '../config.js';
let configPath = path.join(path.resolve(__dirname, '../../'), 'runtime', 'config.json'); let runtimePath = config.runtime_path;
fs.ensureDirSync( runtimePath );
fs.ensureDirSync( path.join(runtimePath, 'log'));
let configPath = path.join(runtimePath, 'config.json')
fs.writeFileSync(configPath, fs.writeFileSync(configPath,
JSON.stringify(config, null, '\t'), JSON.stringify(config, null, '\t'),

View File

@ -1,18 +1,17 @@
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import config from '../runtime/config.json'; import config from '../../config.json';
let insts = new Map(); let insts = new Map();
let mail; let mail;
const WEBROOT = path.resolve(__dirname, '..'); //路径 const WEBROOT = path.resolve(__dirname, '..'); //路径
const WEBROOT_SERVER = __dirname; const WEBROOT_SERVER = __dirname;
const WEBROOT_RUNTIME = path.join(WEBROOT, 'runtime'); const WEBROOT_RUNTIME = path.resolve(__dirname, '../..');
const WEBROOT_LOG = path.join(WEBROOT_RUNTIME, 'log'); const WEBROOT_LOG = path.join(WEBROOT_RUNTIME, 'log');
const WEBCONFIG = config; const WEBCONFIG = config;
fs.ensureDirSync(WEBROOT_RUNTIME);
fs.ensureDirSync(WEBROOT_LOG); fs.ensureDirSync(WEBROOT_LOG);
if (WEBCONFIG.mail) { if (WEBCONFIG.mail) {

View File

@ -1,20 +0,0 @@
'use strict';
module.exports = {
'port': '3000',
'webhost': 'yapi.local.qunar.com',
'adminAccount': 'admin@admin.com',
'db': {
'servername': '127.0.0.1',
'DATABASE': 'yapi',
'port': 27017
},
'mail': {
'host': 'smtp.mail.com',
'port': 4652,
'auth': {
'user': '****@mail.com',
'pass': '**********'
}
}
};

View File

@ -141,7 +141,7 @@ exports.sendMail = function (options, cb) {
try { try {
_yapi2.default.mail.sendMail({ _yapi2.default.mail.sendMail({
from: _yapi2.default.WEBCONFIG.mail.auth.user, from: _yapi2.default.WEBCONFIG.mail.from,
to: options.to, to: options.to,
subject: 'yapi平台', subject: 'yapi平台',
html: options.contents html: options.contents

View File

@ -18,6 +18,9 @@ var _config2 = _interopRequireDefault(_config);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var configPath = _path2.default.join(_path2.default.resolve(__dirname, '../../'), 'runtime', 'config.json'); var runtimePath = _config2.default.runtime_path;
_fsExtra2.default.ensureDirSync(runtimePath);
_fsExtra2.default.ensureDirSync(_path2.default.join(runtimePath, 'log'));
var configPath = _path2.default.join(runtimePath, 'config.json');
_fsExtra2.default.writeFileSync(configPath, (0, _stringify2.default)(_config2.default, null, '\t'), { encoding: 'utf8' }); _fsExtra2.default.writeFileSync(configPath, (0, _stringify2.default)(_config2.default, null, '\t'), { encoding: 'utf8' });

View File

@ -16,7 +16,7 @@ var _nodemailer = require('nodemailer');
var _nodemailer2 = _interopRequireDefault(_nodemailer); var _nodemailer2 = _interopRequireDefault(_nodemailer);
var _config = require('../runtime/config.json'); var _config = require('../../config.json');
var _config2 = _interopRequireDefault(_config); var _config2 = _interopRequireDefault(_config);
@ -27,11 +27,10 @@ var mail = void 0;
var WEBROOT = _path2.default.resolve(__dirname, '..'); //路径 var WEBROOT = _path2.default.resolve(__dirname, '..'); //路径
var WEBROOT_SERVER = __dirname; var WEBROOT_SERVER = __dirname;
var WEBROOT_RUNTIME = _path2.default.join(WEBROOT, 'runtime'); var WEBROOT_RUNTIME = _path2.default.resolve(__dirname, '../..');
var WEBROOT_LOG = _path2.default.join(WEBROOT_RUNTIME, 'log'); var WEBROOT_LOG = _path2.default.join(WEBROOT_RUNTIME, 'log');
var WEBCONFIG = _config2.default; var WEBCONFIG = _config2.default;
_fsExtra2.default.ensureDirSync(WEBROOT_RUNTIME);
_fsExtra2.default.ensureDirSync(WEBROOT_LOG); _fsExtra2.default.ensureDirSync(WEBROOT_LOG);
if (WEBCONFIG.mail) { if (WEBCONFIG.mail) {

39
service.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
prog="/var/soft/node-v6.11.1-linux-x64/bin/pm2"
app="/home/q/www/yapi.beta.corp.qunar.com/webapp/server_dist/app.js"
start() {
echo "Starting Server..."
eval "$prog start $app --name=yapi"
}
stop() {
echo "Stopping Server..."
eval "$prog stop yapi"
}
restart() {
echo "Restart Server..."
eval "$prog restart yapi"
}
case "$1" in
start)
start && exit 0
;;
stop)
stop || exit 0
;;
restart)
restart || exit 0
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 2
esac