diff --git a/.gitignore b/.gitignore index bcb195a..98fe359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +/dist .DS_Store node_modules bower_components coverage npm-debug.log -tmp \ No newline at end of file +tmp diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d6bbf93 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // 使用 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" + } + ] +} diff --git a/config/index.js b/config/index.js deleted file mode 100644 index 4b818df..0000000 --- a/config/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// local or development or production -module.exports = - (process.env.NODE_ENV === 'local' && require('./config.local')) || - (process.env.NODE_ENV === 'development' && require('./config.dev')) || - require('./config.prod') diff --git a/package.json b/package.json index a567fb2..ceec937 100644 --- a/package.json +++ b/package.json @@ -2,24 +2,21 @@ "name": "rap2-delos", "version": "1.0.0", "repository": { - "url": "" + "url": "https://github.com/thx/rap2-delos" }, "description": "", - "main": "dispatch.js", + "main": "dist/dispatch.js", "scripts": { - "create-db": "node scripts/init", - "dev": "NODE_ENV=development nodemon --watch scripts --watch src scripts/dev.js", - "dev-local": "NODE_ENV=local nodemon --watch scripts --watch src scripts/dev.js", - "start": "NODE_ENV=production node dispatch.js", - "check": "npm run linter;npm run test;", - "test": "NODE_ENV=development TEST_MODE=true mocha --exit --reporter nyan", - "linter": "standard --fix", - "watch-test": "NODE_ENV=development nodemon --watch scripts --watch src --watch test ./node_modules/.bin/mocha --timeout 5000", - "watch-test-local": "NODE_ENV=local nodemon --watch scripts --watch src --watch test ./node_modules/.bin/mocha --timeout 5000" + "init": "node dist/scripts/init/index.js", + "dev": "cross-env NODE_ENV=development nodemon --watch scripts --watch dist dist/scripts/dev.js", + "start": "cross-env NODE_ENV=production node dist/dispatch.js" }, - "author": "mozhi.gyy@alibaba-inc.com, bosn@outlook.com", + "author": "bosn, nuysoft", "license": "ISC", "dependencies": { + "@types/koa": "^2.0.43", + "cross-env": "^5.1.1", + "@types/koa-router": "^7.0.27", "chalk": "^1.1.3", "graceful": "^1.0.1", "js-beautify": "^1.6.9", @@ -34,33 +31,34 @@ "mockjs": "^1.0.1-beta3", "moment": "^2.17.1", "mysql": "^2.11.1", + "mysql2": "^1.5.1", "node-fetch": "^1.7.1", "node-print": "0.0.4", "path-to-regexp": "^2.1.0", - "sequelize": "^3.30.4", - "sequelize-cli": "^3.1.0", + "reflect-metadata": "^0.1.10", + "sequelize": "^4.28.6", + "sequelize-typescript": "^0.6.2", + "svg-captcha": "^1.3.11", "underscore": "^1.8.3", "urllib": "^2.22.0" }, "devDependencies": { + "@types/node": "^8.5.2", "babel-eslint": "^7.2.3", "chai": "^3.5.0", "mocha": "^4.0.1", "nodemon": "^1.11.0", "npm-run-all": "^4.0.2", - "pre-commit": "^1.2.2", "standard": "^10.0.2", - "supertest": "^3.0.0" + "supertest": "^3.0.0", + "tslint": "^5.8.0" }, "standard": { "parser": "babel-eslint", "globals": [], "ignore": [] }, - "pre-commit": [ - "linter" - ], "engines": { - "install-node": "9.2.0" + "install-node": "8.9.3" } } diff --git a/public/index.html b/public/index.html index f9d5bf6..a9acd30 100644 --- a/public/index.html +++ b/public/index.html @@ -7,6 +7,7 @@ RAP2 Delos - https://rap2.alibaba-inc.com + RAP2后端服务已启动,请从前端服务(rap2-dolores)访问。 + RAP2 back-end server is started, please visit via front-end service (rap2-dolores). diff --git a/rap2-delos.release b/rap2-delos.release deleted file mode 100644 index a6cd8ec..0000000 --- a/rap2-delos.release +++ /dev/null @@ -1,6 +0,0 @@ -# 1. 你可以直接编辑本文件的内容,或者通过工具来帮你校验合法性和自动生成,请点击:http://aliwing.alibaba-inc.com/apprelease/home.htm -# 2. 更多关于Release文件的规范和约定,请点击: http://docs.alibaba-inc.com/pages/viewpage.action?pageId=252891532 - -# 构建源码语言类型 -code.language=nodejs - diff --git a/config/config.dev.js b/src/config/config.dev.ts similarity index 70% rename from config/config.dev.js rename to src/config/config.dev.ts index cb084a4..32a48dd 100644 --- a/config/config.dev.js +++ b/src/config/config.dev.ts @@ -1,4 +1,6 @@ -module.exports = { +import { IConfigOptions } from "../types"; + +let config:IConfigOptions = { version: '2.3', serve: { port: 8080 @@ -10,9 +12,9 @@ module.exports = { db: { dialect: 'mysql', host: 'localhost', - port: '3306', + port: 3306, username: 'root', - password: '', // KeyCenter 配置项密文 + password: '', database: 'RAP2_DELOS_APP', pool: { max: 5, @@ -22,3 +24,5 @@ module.exports = { logging: false } } + +export default config diff --git a/config/config.local.js b/src/config/config.local.ts similarity index 75% rename from config/config.local.js rename to src/config/config.local.ts index edf8fb9..a6022e5 100644 --- a/config/config.local.js +++ b/src/config/config.local.ts @@ -1,4 +1,6 @@ -module.exports = { +import { IConfigOptions } from "../types"; + +let config:IConfigOptions = { version: '2.3', serve: { port: 8080 @@ -10,7 +12,7 @@ module.exports = { db: { dialect: 'mysql', host: 'localhost', - port: '3306', + port: 3306, username: 'root', password: '', database: 'RAP2_DELOS_APP_LOCAL', @@ -22,3 +24,5 @@ module.exports = { logging: true } } + +export default config \ No newline at end of file diff --git a/config/config.prod.js b/src/config/config.prod.ts similarity index 74% rename from config/config.prod.js rename to src/config/config.prod.ts index b38f4cc..eada40e 100644 --- a/config/config.prod.js +++ b/src/config/config.prod.ts @@ -1,4 +1,5 @@ -module.exports = { +import { IConfigOptions } from "../types"; +let config:IConfigOptions = { version: '2.3', serve: { port: 8080 @@ -10,7 +11,7 @@ module.exports = { db: { dialect: 'mysql', host: 'localhost', - port: '3306', + port: 3306, username: 'root', password: '', database: 'RAP2_DELOS_APP', @@ -22,3 +23,5 @@ module.exports = { logging: true } } + +export default config diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..131ad19 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,9 @@ +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 \ No newline at end of file diff --git a/dispatch.js b/src/dispatch.ts similarity index 66% rename from dispatch.js rename to src/dispatch.ts index 7f65657..a839065 100644 --- a/dispatch.js +++ b/src/dispatch.ts @@ -1,12 +1,6 @@ -// process.env.NODE_ENV = 'production' +import * as cluster from 'cluster' +import * as path from 'path' -// http://gitlab.alibaba-inc.com/thx/rap2-dolores/commit/2fd70fdcaa9d179e9cf95e530e37f31f8488f432 -// https://nodejs.org/api/cluster.html -// https://github.com/node-modules/graceful/blob/master/example/express_with_cluster/dispatch.js -// http://gitlab.alibaba-inc.com/mm/fb/blob/master/dispatch.js - -let cluster = require('cluster') -let path = require('path') let now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '') cluster.setupMaster({ diff --git a/src/models/helper.js b/src/models/helper.ts similarity index 92% rename from src/models/helper.js rename to src/models/helper.ts index 77a44c3..7e7297d 100644 --- a/src/models/helper.js +++ b/src/models/helper.ts @@ -1,5 +1,5 @@ const Sequelize = require('sequelize') -module.exports = { +export let Helper = { id: { type: Sequelize.BIGINT(11).UNSIGNED, primaryKey: true, allowNull: false, autoIncrement: true, comment: '唯一标识' }, include: [], exclude: { diff --git a/src/models/index.js b/src/models/index.js deleted file mode 100644 index f84b67b..0000000 --- a/src/models/index.js +++ /dev/null @@ -1,111 +0,0 @@ -// TODO 2.2 如何缓存重复查询?https://github.com/rfink/sequelize-redis-cache -const Helper = require('./helper') -const User = require('./user') -const Organization = require('./organization') -const Repository = require('./repository') -const Module = require('./module') -const Interface = require('./interface') -const Property = require('./property') -const Logger = require('./logger') -const Notification = require('./notification') - -// http://docs.sequelizejs.com/manual/tutorial/associations.html - -User.OwnedOrganizations = User.hasMany(Organization, { foreignKey: 'ownerId', constraints: false, as: 'ownedOrganizations' }) -User.JoinedOrganizations = User.belongsToMany(Organization, { through: 'organizations_members', as: 'joinedOrganizations' }) - -User.OwnedRepositories = User.hasMany(Repository, { foreignKey: 'ownerId', constraints: false, as: 'ownedRepositories' }) -User.JoinedRepositories = User.belongsToMany(Repository, { through: 'repositories_members', as: 'joinedRepositories' }) - -Organization.Creator = Organization.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者 -Organization.Owner = Organization.belongsTo(User, { foreignKey: 'ownerId', constraints: false, as: 'owner' }) // 所有者 -Organization.Members = Organization.belongsToMany(User, { through: 'organizations_members', as: 'members' }) // 团队成员 -Organization.Repositories = Organization.hasMany(Repository, { foreignKey: 'organizationId', constraints: false, as: 'repositories' }) - -Repository.Creator = Repository.belongsTo(User, { foreignKey: 'creatorId', constraints: false, as: 'creator' }) // 创建者 -Repository.Owner = Repository.belongsTo(User, { foreignKey: 'ownerId', constraints: false, as: 'owner' }) // 所有者 -Repository.Organization = Repository.belongsTo(Organization, { foreignKey: 'organizationId', constraints: false, as: 'organization' }) // 所属团队 -Repository.Locker = Repository.belongsTo(User, { foreignKey: 'lockerId', constraints: false, as: 'locker' }) // 锁定者 -Repository.Members = Repository.belongsToMany(User, { through: 'repositories_members', as: 'members' }) // 仓库成员 -Repository.Modules = Repository.hasMany(Module, { foreignKey: 'repositoryId', constraints: false, as: 'modules' }) -Repository.Interfaces = Repository.hasMany(Interface, { foreignKey: 'repositoryId', constraints: false, as: 'interfaces' }) - -Repository.Collaborators = Repository.belongsToMany(Repository, { through: 'repositories_collaborators', as: 'collaborators' }) // 仓库共享 - -Module.Creator = Module.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者 -Module.Repository = Module.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' }) -Module.Interfaces = Module.hasMany(Interface, { foreignKey: 'moduleId', constraints: false, as: 'interfaces' }) - -Interface.Creator = Interface.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者 -Interface.Locker = Interface.belongsTo(User, { foreignKey: 'lockerId', as: 'locker' }) // 锁定者 -Interface.Module = Interface.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' }) -Interface.Repository = Interface.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' }) -Interface.Properties = Interface.hasMany(Property, { foreignKey: 'interfaceId', constraints: false, as: 'properties' }) - -Property.Creator = Property.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) // 创建者 -Property.Interface = Property.belongsTo(Interface, { foreignKey: 'interfaceId', as: 'interface' }) -Property.Module = Property.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' }) -Property.Repository = Property.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' }) - -Logger.Creator = Logger.belongsTo(User, { foreignKey: 'creatorId', as: 'creator' }) -Logger.User = Logger.belongsTo(User, { foreignKey: 'userId', as: 'user' }) -Logger.Repository = Logger.belongsTo(Repository, { foreignKey: 'repositoryId', as: 'repository' }) -Logger.Organization = Logger.belongsTo(Organization, { foreignKey: 'organizationId', as: 'organization' }) -Logger.Module = Logger.belongsTo(Module, { foreignKey: 'moduleId', as: 'module' }) -Logger.Interface = Logger.belongsTo(Interface, { foreignKey: 'interfaceId', as: 'interface' }) - -const QueryInclude = { - User: { model: User, as: 'user', attributes: { exclude: ['password', ...Helper.exclude.generalities] }, required: true }, - 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, - separate: true, - order: [ - ['priority', 'ASC'] - ], - include: [{ - model: Interface, - as: 'interfaces', - attributes: { exclude: [] }, - required: false, - separate: true, - order: [ - ['priority', 'ASC'] - ], - include: [{ - model: User, - as: 'locker', - attributes: { exclude: ['password', ...Helper.exclude.generalities] }, - required: false - }, { - model: Property, - as: 'properties', - attributes: { exclude: [] }, - required: false, - separate: true - }] - }] - }, - Properties: { - model: Property, - as: 'properties', - attributes: { exclude: [] }, - required: false - } -} - -module.exports = { - User, Organization, Repository, Module, Interface, Property, Logger, Notification, Helper, QueryInclude -} diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..d075b86 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,11 @@ +import { User } from '.'; + +export { QueryInclude } from './queryInclude' +export { Interface } from './interface' +export { Logger } from './logger' +export { Module } from './module' +export { Notification } from './notification' +export { Organization } from './organization' +export { Property } from './property' +export { Repository } from './repository' +export { User } from './user' \ No newline at end of file diff --git a/src/models/interface.js b/src/models/interface.js deleted file mode 100644 index 8bfee2a..0000000 --- a/src/models/interface.js +++ /dev/null @@ -1,16 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -const methods = ['GET', 'POST', 'PUT', 'DELETE'] -module.exports = sequelize.define('interface', { - id, - name: { type: Sequelize.STRING(256), allowNull: false, comment: '接口名称' }, - url: { type: Sequelize.STRING(256), allowNull: false, comment: '接口地址' }, - method: { type: Sequelize.ENUM(...methods), allowNull: false, comment: '接口类型' }, - description: { type: Sequelize.TEXT, allowNull: true, comment: '接口描述' }, - // DONE 2.2 支持接口排序 - priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' } -}, { - paranoid: true, - comment: '接口' -}) diff --git a/src/models/interface.ts b/src/models/interface.ts new file mode 100644 index 0000000..b8495a5 --- /dev/null +++ b/src/models/interface.ts @@ -0,0 +1,70 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'sequelize-typescript' +import { User, Module, Repository, Property } from './index'; + +enum methods { GET= 'GET', POST= 'POST', PUT= 'PUT', DELETE= 'DELETE' } + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Interface extends Model { + + 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({ type: DataType.ENUM(methods.GET, methods.POST, methods.PUT, methods.DELETE), comment: 'API method' }) + method: string + + @Column(DataType.TEXT) + description + + @AllowNull(false) + @Default(1) + @Column({ type: DataType.BIGINT(11).UNSIGNED, comment: 'Priority used for ordering' }) + priority: 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[] + +} \ No newline at end of file diff --git a/src/models/logger.js b/src/models/logger.js deleted file mode 100644 index eee1ab6..0000000 --- a/src/models/logger.js +++ /dev/null @@ -1,18 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -const types = ['create', 'update', 'delete', 'lock', 'unlock', 'join', 'exit'] -// DONE 2.3 需要加 creator 吗?Xxx 把 Xxx 加入了 仓库Xxx 或 团队Xxx。 -module.exports = sequelize.define('logger', { - id, - type: { type: Sequelize.ENUM(...types), allowNull: false, comment: '操作类型' }, - creatorId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '创建者' }, - userId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, comment: '涉及用户' }, - organizationId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及组织' }, - repositoryId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及仓库' }, - moduleId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及模块' }, - interfaceId: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: true, comment: '涉及接口' } -}, { - paranoid: true, - comment: '操作日志' -}) diff --git a/src/models/logger.ts b/src/models/logger.ts new file mode 100644 index 0000000..a52905e --- /dev/null +++ b/src/models/logger.ts @@ -0,0 +1,68 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'sequelize-typescript' +import { User, Repository, Organization, Module, Interface } from './index' + +enum types { + CREATE = 'create', UPDATE = 'update', DELETE = 'delete', + LOCK = 'lock', UNLOCK = 'unlock', JOIN = 'join', EXIT = 'exit' +} + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Logger extends Model { + 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(DataType.BIGINT(11).UNSIGNED) + creatorId: number + + @AllowNull(false) + @ForeignKey(() => User) + @Column(DataType.BIGINT(11).UNSIGNED) + userId: number + + @ForeignKey(() => Organization) + @Column(DataType.BIGINT(11).UNSIGNED) + organizationId: number + + @ForeignKey(() => Repository) + @Column(DataType.BIGINT(11).UNSIGNED) + repositoryId: number + + @ForeignKey(() => Module) + @Column(DataType.BIGINT(11).UNSIGNED) + moduleId: number + + @ForeignKey(() => Interface) + @Column(DataType.BIGINT(11).UNSIGNED) + 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 + +} \ No newline at end of file diff --git a/src/models/module.js b/src/models/module.js deleted file mode 100644 index 76a3b9b..0000000 --- a/src/models/module.js +++ /dev/null @@ -1,12 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -module.exports = sequelize.define('module', { - id, - name: { type: Sequelize.STRING(256), allowNull: false, comment: '模块名称' }, - description: { type: Sequelize.TEXT, comment: '模块描述' }, - priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' } -}, { - paranoid: true, - comment: '模块' -}) diff --git a/src/models/module.ts b/src/models/module.ts new file mode 100644 index 0000000..e945557 --- /dev/null +++ b/src/models/module.ts @@ -0,0 +1,42 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'sequelize-typescript' +import {User, Repository, Interface} from './index' + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Module extends Model { + + @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(11).UNSIGNED) + 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[] +} \ No newline at end of file diff --git a/src/models/notification.js b/src/models/notification.js deleted file mode 100644 index f996b8a..0000000 --- a/src/models/notification.js +++ /dev/null @@ -1,16 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -module.exports = sequelize.define('notification', { - id, - fromId: { type: Sequelize.BIGINT(11), allowNull: true, comment: '发送者' }, - toId: { type: Sequelize.BIGINT(11), allowNull: false, comment: '接受者' }, - type: { type: Sequelize.STRING(128), allowNull: false, comment: '消息类型' }, - param1: { type: Sequelize.STRING(128), allowNull: true, comment: '参数1' }, - param2: { type: Sequelize.STRING(128), allowNull: true, comment: '参数2' }, - param3: { type: Sequelize.STRING(128), allowNull: true, comment: '参数3' }, - readed: { type: Sequelize.BOOLEAN, allowNull: false, defautValue: false, comment: '是否已读' } -}, { - paranoid: true, - comment: '消息' -}) diff --git a/src/models/notification.ts b/src/models/notification.ts new file mode 100644 index 0000000..cbffb63 --- /dev/null +++ b/src/models/notification.ts @@ -0,0 +1,35 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default } from 'sequelize-typescript' + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Notification extends Model { + + @AutoIncrement + @PrimaryKey + @Column + id: number + + @Column({ type: DataType.BIGINT(11).UNSIGNED, comment: 'sender' }) + fromId: number + + @AllowNull(false) + @Column({ type: DataType.BIGINT(11).UNSIGNED, comment: 'receiver' }) + toId: number + + @AllowNull(false) + @Column({ type: DataType.STRING(128), 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 +} \ No newline at end of file diff --git a/src/models/organization.js b/src/models/organization.js deleted file mode 100644 index f5c41b2..0000000 --- a/src/models/organization.js +++ /dev/null @@ -1,13 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -module.exports = sequelize.define('organization', { - id, - name: { type: Sequelize.STRING(256), allowNull: false, comment: '团队名称' }, - description: { type: Sequelize.TEXT, allowNull: true, comment: '团队描述' }, - logo: { type: Sequelize.STRING(256), allowNull: true, comment: '团队标志' }, - visibility: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true, comment: '是否公开' } -}, { - paranoid: true, - comment: '团队' -}) diff --git a/src/models/organization.ts b/src/models/organization.ts new file mode 100644 index 0000000..4ee1f3c --- /dev/null +++ b/src/models/organization.ts @@ -0,0 +1,46 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMany, ForeignKey } from 'sequelize-typescript' +import { User, Repository } from './index' + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Organization extends Model { + + @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, 'organizations_members', 'organizationId', 'userId') + members: User[] + + @HasMany(() => Repository, 'organizationId') + repositories: Repository[] +} \ No newline at end of file diff --git a/src/models/property.js b/src/models/property.js deleted file mode 100644 index 0532d5f..0000000 --- a/src/models/property.js +++ /dev/null @@ -1,19 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -const SCOPES = ['request', 'response'] -const TYPES = ['String', 'Number', 'Boolean', 'Object', 'Array', 'Function', 'RegExp'] -module.exports = sequelize.define('property', { - id, - scope: { type: Sequelize.ENUM(...SCOPES), allowNull: false, defaultValue: 'response', comment: '属性归属' }, - name: { type: Sequelize.STRING(256), allowNull: false, comment: '属性名称' }, - type: { type: Sequelize.ENUM(...TYPES), allowNull: false, comment: '属性值类型' }, - rule: { type: Sequelize.STRING(128), allowNull: true, comment: '属性值生成规则' }, - value: { type: Sequelize.TEXT, allowNull: true, comment: '属性值' }, - description: { type: Sequelize.TEXT, allowNull: true, comment: '属性描述' }, - parentId: { type: Sequelize.BIGINT(11), allowNull: false, defaultValue: -1, comment: '父属性' }, - priority: { type: Sequelize.BIGINT(11).UNSIGNED, allowNull: false, defaultValue: 1, comment: '接口优先级' } -}, { - paranoid: true, - comment: '属性' -}) diff --git a/src/models/property.ts b/src/models/property.ts new file mode 100644 index 0000000..12d004e --- /dev/null +++ b/src/models/property.ts @@ -0,0 +1,86 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, ForeignKey } from 'sequelize-TYPEScript' +import { User, Interface, Module, Repository } from './index' + +enum SCOPES { REQUEST = 'request', RESPONSE = 'response' } +enum TYPES { STRING = 'String', NUMBER = 'Number', BOOLEAN = 'Boolean', OBJECT = 'Object', ARRAY = 'Array', FUNCTION = 'Function', REGEXP = 'RegExp' } + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Property extends Model { + 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), + comment: 'property type' + }) + type: string + + @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({ type: DataType.BIGINT(11), comment: 'parent property ID' }) + parentId: number + + @AllowNull(false) + @Default(1) + @Column(DataType.BIGINT(11).UNSIGNED) + 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 + +} \ No newline at end of file diff --git a/src/models/queryInclude.ts b/src/models/queryInclude.ts new file mode 100644 index 0000000..2373d90 --- /dev/null +++ b/src/models/queryInclude.ts @@ -0,0 +1,60 @@ +// TODO 2.2 如何缓存重复查询?https://github.com/rfink/sequelize-redis-cache +import { Helper } from './helper' +import { User } from './user' +import { Repository } from './repository'; +import { Organization } from './organization' +import { Module } from './module' +import { Interface } from './interface' +import { Property } from './property' + +export const QueryInclude = { + 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, + separate: true, + order: [ + ['priority', 'ASC'] + ], + include: [{ + model: Interface, + as: 'interfaces', + attributes: { exclude: [] }, + required: false, + separate: true, + order: [ + ['priority', 'ASC'] + ], + include: [{ + model: User, + as: 'locker', + attributes: { exclude: ['password', ...Helper.exclude.generalities] }, + required: false + }, { + model: Property, + as: 'properties', + attributes: { exclude: [] }, + required: false, + separate: true + }] + }] + }, + Properties: { + model: Property, + as: 'properties', + attributes: { exclude: [] }, + required: false + } +} \ No newline at end of file diff --git a/src/models/repository.js b/src/models/repository.js deleted file mode 100644 index 97e03ed..0000000 --- a/src/models/repository.js +++ /dev/null @@ -1,13 +0,0 @@ -const Sequelize = require('sequelize') -const sequelize = require('./sequelize') -const { id } = require('./helper') -module.exports = sequelize.define('repository', { - id, - name: { type: Sequelize.STRING(256), allowNull: false, comment: '仓库名称' }, - description: { type: Sequelize.TEXT, allowNull: true, comment: '仓库描述' }, - logo: { type: Sequelize.STRING(256), allowNull: true, comment: '仓库标志' }, - visibility: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true, comment: '是否公开' } -}, { - paranoid: true, - comment: '仓库' -}) diff --git a/src/models/repository.ts b/src/models/repository.ts new file mode 100644 index 0000000..b24bb3c --- /dev/null +++ b/src/models/repository.ts @@ -0,0 +1,67 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, BelongsTo, BelongsToMany, ForeignKey } from 'sequelize-typescript' +import { User, Organization, Module, Interface } from './index' + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class Repository extends Model { + + @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 + 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, 'repositories_collaborators', 'repositoryId', 'collaboratorId') + collaborators: Repository[] + +} \ No newline at end of file diff --git a/src/models/sequelize.ts b/src/models/sequelize.ts new file mode 100644 index 0000000..679843e --- /dev/null +++ b/src/models/sequelize.ts @@ -0,0 +1,48 @@ +import { Sequelize } from 'sequelize-typescript' +import { Interface, Logger, Module, Notification, Organization, Property, User, Repository } from './index' +import config from '../config' + +const chalk = require('chalk') +const now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '') +const logging = process.env.NODE_ENV === 'development' + ? (sql) => { + sql = sql.replace('Executing (default): ', '') + console.log(`${chalk.bold('SQL')} ${now()} ${chalk.gray(sql)}`) + } + : console.log + +const sequelize = new Sequelize({ + database: config.db.database, + 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 +}) + +sequelize.addModels([Interface, Logger, Module, Notification, Organization, Property, Repository, User]) +sequelize.authenticate() + .then((/* err */) => { + + // initialize hooks + Organization.hook('afterCreate', async(instance, options) => { + await Logger.create({ + userId: instance.creatorId, + type: 'create', + organizationId: instance.id + }) + }) + console.log('----------------------------------------') + console.log('DATABASE √') + console.log(' HOST %s', config.db.host) + console.log(' PORT %s', config.db.port) + console.log(' DATABASE %s', config.db.database) + console.log('----------------------------------------') + }) + .catch(err => { + console.log('Unable to connect to the database:', err) + }) + +module.exports = sequelize \ No newline at end of file diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000..6ef1e69 --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,36 @@ +import { Sequelize, Table, Column, Model, HasMany, AutoIncrement, PrimaryKey, AllowNull, DataType, Default, Unique, ForeignKey, BelongsToMany } from 'sequelize-typescript' +import { Organization, Repository } from './index' + +@Table({ paranoid: true, freezeTableName: false, timestamps: true }) +export class User extends Model { + + @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, 'organizations_members', 'organizationId', 'userId') + joinedOrganizations: Organization[] + + @HasMany(() => Repository, 'ownerId') + ownedRepositories: Repository[] + + @BelongsToMany(() => Repository, 'repositories_members', 'userId', 'repositoryId') + joinedRepositories: Repository[] + +} \ No newline at end of file diff --git a/src/routes/account.js b/src/routes/account.ts similarity index 79% rename from src/routes/account.js rename to src/routes/account.ts index 666873c..4025791 100644 --- a/src/routes/account.js +++ b/src/routes/account.ts @@ -1,6 +1,14 @@ -let router = require('./router') -let Pagination = require('./utils/pagination') -let { User, Notification, Logger, QueryInclude } = require('../models') +import * as svgCaptcha from 'svg-captcha' +import { User, Notification, Logger, Organization, Repository } from '../models' +import router from './router' +import { Model } from 'sequelize-typescript'; +import { WhereOptions } from 'sequelize'; +import { Pagination } from './utils/pagination' +import { QueryInclude } from '../models' + +interface Application { + counter: any +} router.get('/app/get', async (ctx, next) => { let data = {} @@ -60,9 +68,15 @@ router.get('/account/info', async(ctx, next) => { }) : null } }) + router.post('/account/login', async(ctx, next) => { - let { email, password } = ctx.request.body - let result = await User.findOne({ + let { email, password, captcha } = ctx.request.body + let result, errMsg + + if (!captcha || !ctx.session.captcha || captcha.trim().toLowerCase() !== ctx.session.captcha.toLowerCase()) { + errMsg = '错误的验证码' + } + result = await User.findOne({ attributes: QueryInclude.User.attributes, where: { email, password } }) @@ -87,7 +101,6 @@ router.get('/account/logout', async(ctx, next) => { }) router.post('/account/register', async(ctx, next) => { - // TODO 2.4 empId 可能为空,需要重新梳理用户注册流程 let { fullname, email, password } = ctx.request.body let exists = await User.findAll({ where: { email } @@ -144,6 +157,7 @@ router.get('/account/setting', async(ctx, next) => { data: {} } }) + router.post('/account/setting', async(ctx, next) => { ctx.body = { data: {} @@ -167,16 +181,19 @@ router.get('/account/notification/list', async(ctx, next) => { pagination: pagination } }) + router.get('/account/notification/unreaded', async(ctx, next) => { ctx.body = { data: [] } }) + router.post('/account/notification/unreaded', async(ctx, next) => { ctx.body = { data: 0 } }) + router.post('/account/notification/read', async(ctx, next) => { ctx.body = { data: 0 @@ -186,10 +203,10 @@ router.post('/account/notification/read', async(ctx, next) => { // TODO 2.3 账户日志 router.get('/account/logger', async(ctx, next) => { let auth = await User.findById(ctx.session.id) - let repositories = [...await auth.getOwnedRepositories({}), ...await auth.getJoinedRepositories({})] - let organizations = [...await auth.getOwnedOrganizations({}), ...await auth.getJoinedOrganizations({})] + let repositories:Model[] = [...([]>await auth.$get('ownedRepositories')), ...([]> await auth.$get('joinedRepositories'))] + let organizations:Model[] = [...([]>await auth.$get('ownedOrganizations')), ...([]> await auth.$get('joinedOrganizations'))] - let where = { + let where:any = { $or: [ { userId: ctx.session.id }, { repositoryId: repositories.map(item => item.id) }, @@ -198,6 +215,7 @@ router.get('/account/logger', async(ctx, next) => { } let total = await Logger.count({ where }) let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100) + console.log(QueryInclude.User) let logs = await Logger.findAll({ where, attributes: {}, @@ -216,10 +234,16 @@ router.get('/account/logger', async(ctx, next) => { ], paranoid: false }) + ctx.body = { data: logs, - pagination: pagination + pagination } }) -module.exports = router +router.get('/captcha', async(ctx, next) => { + var captcha = svgCaptcha.create() + ctx.session.captcha = captcha.text + ctx.set('Content-Type', 'image/svg+xml') + ctx.body = captcha.data +}) \ No newline at end of file diff --git a/src/routes/analytics.js b/src/routes/analytics.ts similarity index 96% rename from src/routes/analytics.js rename to src/routes/analytics.ts index 9e11238..1155ee4 100644 --- a/src/routes/analytics.js +++ b/src/routes/analytics.ts @@ -1,4 +1,4 @@ -const router = require('./router') +import router from './router' const moment = require('moment') const Sequelize = require('sequelize') const SELECT = { type: Sequelize.QueryTypes.SELECT } @@ -62,7 +62,6 @@ router.get('/app/analytics/users/activation', async (ctx, next) => { let sql = ` SELECT loggers.userId AS userId, - users.empId AS empId, users.fullname AS fullname, COUNT(*) AS value FROM @@ -108,4 +107,4 @@ router.get('/app/analytics/repositories/activation', async (ctx, next) => { } }) -// TODO 2.3 支持 start、end +// TODO 2.3 支持 start、end \ No newline at end of file diff --git a/src/routes/counter.js b/src/routes/counter.ts similarity index 66% rename from src/routes/counter.js rename to src/routes/counter.ts index f8c11e5..f5aa557 100644 --- a/src/routes/counter.js +++ b/src/routes/counter.ts @@ -1,5 +1,5 @@ -const router = require('./router') -const config = require('../../config') +import router from './router' +const config = require('../config') router.get('/app/counter', async(ctx, next) => { ctx.body = { @@ -9,6 +9,4 @@ router.get('/app/counter', async(ctx, next) => { mock: ctx.app.counter.mock } } -}) - -module.exports = router +}) \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.ts similarity index 70% rename from src/routes/index.js rename to src/routes/index.ts index e0231e6..435eeee 100644 --- a/src/routes/index.js +++ b/src/routes/index.ts @@ -1,4 +1,4 @@ -let router = require('./router') +import router from './router' require('./counter') require('./account') @@ -7,4 +7,4 @@ require('./repository') require('./mock') require('./analytics') -module.exports = router +export default router diff --git a/src/routes/mock.js b/src/routes/mock.ts similarity index 92% rename from src/routes/mock.js rename to src/routes/mock.ts index 370310c..2522724 100644 --- a/src/routes/mock.js +++ b/src/routes/mock.ts @@ -1,8 +1,8 @@ -const { URL } = require('url') -const router = require('./router') -const { Repository, Interface, Property, QueryInclude } = require('../models') +import router from './router' +import { Repository, Interface, Property } from '../models' +import { QueryInclude } from '../models'; +import Tree from './utils/tree' const attributes = { exclude: [] } -const Tree = require('./utils/tree') const pt = require('node-print').pt const beautify = require('js-beautify').js_beautify const urlUtils = require('./utils/url') @@ -25,7 +25,7 @@ const parseDuplicatedInterfaces = (repository) => { const generatePlugin = (protocol, host, repository) => { // DONE 2.3 protocol 错误,应该是 https let duplicated = parseDuplicatedInterfaces(repository) - let editor = `${protocol}://rap2.alibaba-inc.com/repository/editor?id=${repository.id}` + let editor = `${protocol}://rap2.taobao.org/repository/editor?id=${repository.id}` // [TODO] replaced by cur domain let result = ` /** * 仓库 #${repository.id} ${repository.name} @@ -57,7 +57,7 @@ const generatePlugin = (protocol, host, repository) => { } router.get('/app/plugin/:repositories', async (ctx, next) => { - let repositoryIds = new Set(ctx.params.repositories.split(',').map(item => +item).filter(item => item)) // _.uniq() => Set + let repositoryIds = new Set(ctx.params.repositories.split(',').map(item => +item).filter(item => item)) // _.uniq() => Set let result = [] for (let id of repositoryIds) { let repository = await Repository.findById(id, { @@ -78,7 +78,7 @@ router.get('/app/plugin/:repositories', async (ctx, next) => { }) } // console.log(repositoryIds) - repository.interfaces = await Interface.findAll({ + repository.interfaces = await Interface.findAll({ attributes: { exclude: [] }, where: { repositoryId: repository.id @@ -105,10 +105,10 @@ router.get('/app/plugin/:repositories', async (ctx, next) => { // X DONE 2.2 支持 GET POST PUT DELETE 请求 // DONE 2.2 忽略请求地址中的前缀斜杠 // DONE 2.3 支持所有类型的请求,这样从浏览器中发送跨越请求时不需要修改 method -router.all('/app/mock/(\\d+)/(\\w+)/(.+)', async (ctx, next) => { +router.all('/app/mock/(\\d+)/(.+)', async (ctx, next) => { ctx.app.counter.mock++ - let [ repositoryId, method, url ] = [ctx.params[0], ctx.params[1], ctx.params[2]] + let [ repositoryId, method, url ] = [ctx.params[0], ctx.request.method, ctx.params[1]] let urlWithoutPrefixSlash = /(\/)?(.*)/.exec(url)[2] let urlWithoutSearch @@ -124,7 +124,7 @@ router.all('/app/mock/(\\d+)/(\\w+)/(.+)', async (ctx, next) => { // 所以这里重新解析一遍!!! let repository = await Repository.findById(repositoryId) - let collaborators = await repository.getCollaborators() + let collaborators = await repository.collaborators let itf itf = await Interface.findOne({ diff --git a/src/routes/organization.js b/src/routes/organization.ts similarity index 87% rename from src/routes/organization.js rename to src/routes/organization.ts index 31ee46b..167592d 100644 --- a/src/routes/organization.js +++ b/src/routes/organization.ts @@ -1,15 +1,8 @@ -let _ = require('underscore') -let router = require('./router') -let Pagination = require('./utils/pagination') -let { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger } = require('../models') - -Organization.hook('afterCreate', async(instance, options) => { - await Logger.create({ - userId: instance.creatorId, - type: 'create', - organizationId: instance.id - }) -}) +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' router.get('/app/get', async (ctx, next) => { let data = {} @@ -84,13 +77,13 @@ router.get('/organization/owned', async(ctx, next) => { } let auth = await User.findById(ctx.session.id) - let options = { + let options:any = { where, attributes: { exclude: [] }, include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members], order: [['updatedAt', 'DESC']] } - let owned = await auth.getOwnedOrganizations(options) + let owned = await auth.$get('ownedOrganizations', options) ctx.body = { data: owned, pagination: null @@ -109,13 +102,13 @@ router.get('/organization/joined', async(ctx, next) => { } let auth = await User.findById(ctx.session.id) - let options = { + let options:object = { where, attributes: { exclude: [] }, include: [QueryInclude.Creator, QueryInclude.Owner, QueryInclude.Members], order: [['updatedAt', 'DESC']] } - let joined = await auth.getJoinedOrganizations(options) + let joined = await auth.$get('joinedOrganizations', options) // await auth.getOwnedOrganizations() // await auth.getJoinedOrganizations() ctx.body = { @@ -138,7 +131,7 @@ router.post('/organization/create', async(ctx, next) => { let created = await Organization.create(body) if (body.memberIds) { let members = await User.findAll({ where: { id: body.memberIds } }) - await created.setMembers(members) + await created.$set('members', members) } let filled = await Organization.findById(created.id, { attributes: { exclude: [] }, @@ -157,9 +150,9 @@ router.post('/organization/update', async(ctx, next) => { if (body.memberIds) { let reloaded = await Organization.findById(body.id) let members = await User.findAll({ where: { id: body.memberIds } }) - ctx.prevAssociations = await reloaded.getMembers() - await reloaded.setMembers(members) - ctx.nextAssociations = await reloaded.getMembers() + ctx.prevAssociations = await reloaded.$get('members') + await reloaded.$set('members', members) + ctx.nextAssociations = await reloaded.$get('members') } ctx.body = { data: updated[0] @@ -220,6 +213,4 @@ router.get('/organization/remove', async(ctx, next) => { type: 'delete', organizationId: id }) -}) - -module.exports = router +}) \ No newline at end of file diff --git a/src/routes/repository.js b/src/routes/repository.ts similarity index 94% rename from src/routes/repository.js rename to src/routes/repository.ts index d2fbf9c..46af406 100644 --- a/src/routes/repository.js +++ b/src/routes/repository.ts @@ -1,8 +1,8 @@ // TODO 2.1 大数据测试,含有大量模块、接口、属性的仓库 -const router = require('./router') +import router from './router' const _ = require('underscore') const Pagination = require('./utils/pagination') -const { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger } = require('../models') +import { User, Organization, Repository, Module, Interface, Property, QueryInclude, Logger } from '../models' const Tree = require('./utils/tree') const { initRepository, initModule } = require('./utils/helper') @@ -75,7 +75,7 @@ router.get('/repository/list', async(ctx, next) => { } }) router.get('/repository/owned', async(ctx, next) => { - let where = {} // { organizationId: null } // 彻底废弃个人仓库&团队仓库概念,一个仓库必须属于某个用户,可选属于某个团队 + let where = {} let { name } = ctx.query if (name) { Object.assign(where, { @@ -86,12 +86,11 @@ router.get('/repository/owned', async(ctx, next) => { }) } - let auth = await User.findById(ctx.query.user || ctx.session.id) + let auth: User = await User.findById(ctx.query.user || ctx.session.id) // let total = await auth.countOwnedRepositories({ where }) // let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100) - let repositories = await auth.getOwnedRepositories({ + let repositories = await auth.$get('ownedRepositories', { where, - attributes: { exclude: [] }, include: [ QueryInclude.Creator, QueryInclude.Owner, @@ -110,7 +109,7 @@ router.get('/repository/owned', async(ctx, next) => { } }) router.get('/repository/joined', async(ctx, next) => { - let where = {} + let where:any = {} let { name } = ctx.query if (name) { Object.assign(where, { @@ -124,7 +123,7 @@ router.get('/repository/joined', async(ctx, next) => { let auth = await User.findById(ctx.query.user || ctx.session.id) // let total = await auth.countJoinedRepositories({ where }) // let pagination = new Pagination(total, ctx.query.cursor || 1, ctx.query.limit || 100) - let repositories = await auth.getJoinedRepositories({ + let repositories = await auth.$get('joinedRepositories', { where, attributes: { exclude: [] }, include: [ @@ -167,11 +166,11 @@ router.post('/repository/create', async(ctx, next) => { let created = await Repository.create(body) if (body.memberIds) { let members = await User.findAll({ where: { id: body.memberIds } }) - await created.setMembers(members) + await created.$set('members', members) } if (body.collaboratorIds) { let collaborators = await Repository.findAll({ where: { id: body.collaboratorIds } }) - await created.setCollaborators(collaborators) + await created.$set('collaborators', collaborators) } await initRepository(created) ctx.body = { @@ -206,14 +205,14 @@ router.post('/repository/update', async(ctx, next) => { if (body.memberIds) { let reloaded = await Repository.findById(body.id) let members = await User.findAll({ where: { id: body.memberIds } }) - ctx.prevAssociations = await reloaded.getMembers() - await reloaded.setMembers(members) - ctx.nextAssociations = await reloaded.getMembers() + ctx.prevAssociations = await reloaded.$get('members') + await reloaded.$set('members', members) + ctx.nextAssociations = await reloaded.$get('members') } if (body.collaboratorIds) { let reloaded = await Repository.findById(body.id) let collaborators = await Repository.findAll({ where: { id: body.collaboratorIds } }) - await reloaded.setCollaborators(collaborators) + await reloaded.$set('collaborators', collaborators) } ctx.body = { data: result[0] @@ -242,7 +241,7 @@ router.post('/repository/update', async(ctx, next) => { }) router.post('/repository/transfer', async(ctx, next) => { let { id, ownerId, organizationId } = ctx.request.body - let body = {} + let body:any = {} if (ownerId) body.ownerId = ownerId // 转移给其他用户 if (organizationId) { body.organizationId = organizationId // 转移给其他团队,同时转移给该团队拥有者 @@ -305,7 +304,7 @@ router.get('/module/count', async (ctx, next) => { } }) router.get('/module/list', async (ctx, next) => { - let where = {} + let where:any = {} let { repositoryId, name } = ctx.query if (repositoryId) where.repositoryId = repositoryId if (name) where.name = { $like: `%${name}%` } @@ -400,7 +399,7 @@ router.get('/interface/count', async (ctx, next) => { } }) router.get('/interface/list', async (ctx, next) => { - let where = {} + let where:any = {} let { repositoryId, moduleId, name } = ctx.query if (repositoryId) where.repositoryId = repositoryId if (moduleId) where.moduleId = moduleId @@ -424,12 +423,12 @@ router.get('/interface/get', async (ctx, next) => { // 同 /app/mock/:repository/:method/:url let urlWithoutPrefixSlash = /(\/)?(.*)/.exec(url)[2] let repository = await Repository.findById(repositoryId) - let collaborators = await repository.getCollaborators() + let collaborators = await repository.$get('collaborators') itf = await Interface.findOne({ attributes: { exclude: [] }, where: { - repositoryId: [repositoryId, ...collaborators.map(item => item.id)], + repositoryId: [repositoryId, ...(collaborators).map(item => item.id)], method, url: [urlWithoutPrefixSlash, '/' + urlWithoutPrefixSlash] } @@ -575,7 +574,7 @@ router.get('/property/count', async (ctx, next) => { } }) router.get('/property/list', async (ctx, next) => { - let where = {} + let where:any = {} let { repositoryId, moduleId, interfaceId, name } = ctx.query if (repositoryId) where.repositoryId = repositoryId if (moduleId) where.moduleId = moduleId @@ -695,6 +694,4 @@ router.get('/property/remove', async (ctx, next) => { where: { id } }) } -}) - -module.exports = router +}) \ No newline at end of file diff --git a/src/routes/router.js b/src/routes/router.ts similarity index 86% rename from src/routes/router.js rename to src/routes/router.ts index e24ab3e..99280df 100644 --- a/src/routes/router.js +++ b/src/routes/router.ts @@ -1,5 +1,5 @@ -let Router = require('koa-router') -const fetch = require('node-fetch') +import * as Router from 'koa-router' +import fetch from 'node-fetch' let router = new Router() // index @@ -32,4 +32,4 @@ router.get('/proxy', async(ctx, next) => { ctx.body = json }) -module.exports = router +export default router \ No newline at end of file diff --git a/src/routes/utils/helper.js b/src/routes/utils/helper.ts similarity index 100% rename from src/routes/utils/helper.js rename to src/routes/utils/helper.ts diff --git a/src/routes/utils/pagination.js b/src/routes/utils/pagination.ts similarity index 76% rename from src/routes/utils/pagination.js rename to src/routes/utils/pagination.ts index 47d0f4f..16f978f 100644 --- a/src/routes/utils/pagination.js +++ b/src/routes/utils/pagination.ts @@ -44,15 +44,34 @@ new Pagination( data, cursor, limit ) new Pagination( total, cursor, limit ) */ -function Pagination (data, cursor, limit) { - this.data = (typeof data === 'number' || typeof data === 'string') ? undefined : data - this.total = this.data ? this.data.length : parseInt(data, 10) - this.cursor = parseInt(cursor, 10) - this.limit = parseInt(limit, 10) - this.calc() -} -Pagination.prototype = { - calc: function () { + +export class Pagination { + public data: any + public total: any + public cursor: number + public limit: number + public focus: number + public pages + public start + public end + public hasPrev + public hasNext + public hasFirst + public hasLast + public prev + public next + public first + public last + + constructor(data, cursor, limit) { + this.data = (typeof data === 'number' || typeof data === 'string') ? undefined : data + this.total = this.data ? this.data.length : parseInt(data, 10) + this.cursor = parseInt(cursor, 10) + this.limit = parseInt(limit, 10) + this.calc() + } + + public calc() { if (this.total && parseInt(this.total, 10) > 0) { this.limit = this.limit < 1 ? 1 : this.limit @@ -87,62 +106,74 @@ Pagination.prototype = { } return this - }, - moveTo: function (cursor) { + } + + public moveTo(cursor) { this.cursor = parseInt(cursor, 10) return this.calc() - }, - moveToPrev: function () { + } + + public moveToPrev() { return this.moveTo(this.cursor - 1) - }, - moveToNext: function () { + } + + public moveToNext() { return this.moveTo(this.cursor + 1) - }, - moveToFirst: function () { + } + + public moveToFirst() { return this.moveTo(1) - }, - moveToLast: function () { + } + + public moveToLast() { return this.moveTo(this.pages) - }, - fetch: function (arr) { + } + + public fetch(arr) { return (arr || this.data).slice(this.start, this.end) - }, - setData: function (data) { + } + + public setData(data) { this.data = data this.total = data.length return this.calc() - }, - setTotal: function (total) { + } + + public setTotal(total) { this.total = parseInt(total, 10) return this.calc() - }, - setCursor: function (cursor) { + } + + public setCursor(cursor) { this.cursor = parseInt(cursor, 10) return this.calc() - }, - setFocus: function (focus) { + } + + public setFocus(focus) { this.focus = parseInt(focus, 10) if (this.focus < 0) this.focus += this.total if (this.focus >= this.total) this.focus -= this.total - this.cursor = parseInt(this.focus / this.limit, 10) + 1 + this.cursor = parseInt(String(this.focus / this.limit), 10) + 1 return this.calc() - }, - setLimit: function (limit) { + } + + public setLimit(limit) { this.limit = parseInt(limit, 10) return this.calc() - }, - get: function (focus) { + } + + public get(focus) { if (focus !== undefined) return this.data[focus % this.data.length] else return this.data[this.focus] - }, - toString: function () { + } + + public toString() { return JSON.stringify(this, null, 4) } -} -Pagination.prototype.to = Pagination.prototype.moveTo -Pagination.prototype.toPrev = Pagination.prototype.moveToPrev -Pagination.prototype.toNext = Pagination.prototype.moveToNext -Pagination.prototype.toFirst = Pagination.prototype.moveToFirst -Pagination.prototype.toLast = Pagination.prototype.moveToLast - -module.exports = Pagination + + public to = this.moveTo + public toPrev = this.moveToPrev + public toNext = this.moveToNext + public toFirst = this.moveToFirst + public toLast = this.moveToLast +} \ No newline at end of file diff --git a/src/routes/utils/tree.js b/src/routes/utils/tree.js deleted file mode 100644 index aa2e425..0000000 --- a/src/routes/utils/tree.js +++ /dev/null @@ -1,173 +0,0 @@ -const vm = require('vm') -const _ = require('underscore') -const Mock = require('mockjs') -const { RE_KEY } = require('mockjs/src/mock/constant') - -const Tree = {} - -Tree.ArrayToTree = (list) => { - let result = { - name: 'root', - children: [], - depth: 0 - } - - let mapped = {} - list.forEach(item => { mapped[item.id] = item }) - - function _parseChildren (parentId, children, depth) { - for (let id in mapped) { - let item = mapped[id] - if (typeof parentId === 'function' ? parentId(item.parentId) : item.parentId === parentId) { - children.push(item) - item.depth = depth + 1 - item.children = _parseChildren(item.id, [], item.depth) - } - } - return children - } - - _parseChildren( - (parentId) => { - // 忽略 parentId 为 0 的根属性(历史遗留),现为 -1 - if (parentId === -1) return true - }, - result.children, - result.depth - ) - - return result -} - -// TODO 2.x 和前端重复了 -Tree.TreeToTemplate = (tree) => { - function parse (item, result) { - let rule = item.rule ? ('|' + item.rule) : '' - let value = item.value - switch (item.type) { - case 'String': - result[item.name + rule] = item.value - break - case 'Number': - if (value === '') value = 1 - let parsed = parseFloat(value) - if (!isNaN(parsed)) value = parsed - result[item.name + rule] = value - break - case 'Boolean': - if (value === 'true') value = true - if (value === 'false') value = false - if (value === '0') value = false - value = !!value - result[item.name + rule] = value - break - case 'Function': - case 'RegExp': - try { - result[item.name + rule] = eval('(' + item.value + ')') // eslint-disable-line no-eval - } catch (e) { - console.warn(`TreeToTemplate ${e.message}: ${item.type} { ${item.name}${rule}: ${item.value} }`) // TODO 2.2 怎么消除异常值? - result[item.name + rule] = item.value - } - break - case 'Object': - if (item.value) { - try { - result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval - } catch (e) { - result[item.name + rule] = item.value - } - } else { - result[item.name + rule] = {} - item.children.forEach((child) => { - parse(child, result[item.name + rule]) - }) - } - break - case 'Array': - if (item.value) { - try { - result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval - } catch (e) { - result[item.name + rule] = item.value - } - } else { - result[item.name + rule] = item.children.length ? [{}] : [] - item.children.forEach((child) => { - parse(child, result[item.name + rule][0]) - }) - } - break - } - } - let result = {} - tree.children.forEach((child) => { - parse(child, result) - }) - return result -} - -Tree.TemplateToData = (template) => { - // 数据模板 template 中可能含有攻击代码,例如死循环,所以在沙箱中生成最终数据 - // https://nodejs.org/dist/latest-v7.x/docs/api/vm.html - const sandbox = { Mock, template, data: {} } - const script = new vm.Script('data = Mock.mock(template)') - const context = new vm.createContext(sandbox) // eslint-disable-line new-cap - try { - script.runInContext(context, { timeout: 1000 }) // 每次 Mock.mock() 最多执行 1s - // DONE 2.1 __root__ - let data = sandbox.data - let keys = Object.keys(data) - if (keys.length === 1 && keys[0] === '__root__') data = data.__root__ - return data - } catch (err) { - console.error(err) - return {} - } -} - -Tree.ArrayToTreeToTemplate = (list) => { - let tree = Tree.ArrayToTree(list) - let template = Tree.TreeToTemplate(tree) - return template -} - -Tree.ArrayToTreeToTemplateToData = (list, extra) => { - let tree = Tree.ArrayToTree(list) - let template = Tree.TreeToTemplate(tree) - let data - - if (extra) { - // DONE 2.2 支持引用请求参数 - let keys = Object.keys(template).map(item => item.replace(RE_KEY, '$1')) - let extraKeys = _.difference(Object.keys(extra), keys) - let scopedData = Tree.TemplateToData( - Object.assign({}, _.pick(extra, extraKeys), template) - ) - data = _.pick(scopedData, keys) - } else { - data = Tree.TemplateToData(template) - } - - return data -} - -Tree.ArrayToTreeToTemplateToJSONSchema = (list) => { - let tree = Tree.ArrayToTree(list) - let template = Tree.TreeToTemplate(tree) - let schema = Mock.toJSONSchema(template) - return schema -} - -// TODO 2.2 执行 JSON.stringify() 序列化时会丢失正则和函数。需要转为字符串或者函数。 -// X Function.protytype.toJSON = Function.protytype.toString -// X RegExp.protytype.toJSON = RegExp.protytype.toString -Tree.stringifyWithFunctonAndRegExp = (json) => { - return JSON.stringify(json, (k, v) => { - if (typeof v === 'function') return v.toString() - if (v !== undefined && v !== null && v.exec) return v.toString() - else return v - }, 2) -} - -module.exports = Tree diff --git a/src/routes/utils/tree.ts b/src/routes/utils/tree.ts new file mode 100644 index 0000000..72c2b9c --- /dev/null +++ b/src/routes/utils/tree.ts @@ -0,0 +1,174 @@ +const vm = require('vm') +const _ = require('underscore') +const Mock = require('mockjs') +const { RE_KEY } = require('mockjs/src/mock/constant') + + +export default class Tree { + + public static ArrayToTree (list) { + let result = { + name: 'root', + children: [], + depth: 0 + } + + let mapped = {} + list.forEach(item => { mapped[item.id] = item }) + + function _parseChildren (parentId, children, depth) { + for (let id in mapped) { + let item = mapped[id] + if (typeof parentId === 'function' ? parentId(item.parentId) : item.parentId === parentId) { + children.push(item) + item.depth = depth + 1 + item.children = _parseChildren(item.id, [], item.depth) + } + } + return children + } + + _parseChildren( + (parentId) => { + // 忽略 parentId 为 0 的根属性(历史遗留),现为 -1 + if (parentId === -1) return true + }, + result.children, + result.depth + ) + + return result + } + + // TODO 2.x 和前端重复了 + public static TreeToTemplate (tree) { + function parse (item, result) { + let rule = item.rule ? ('|' + item.rule) : '' + let value = item.value + switch (item.type) { + case 'String': + result[item.name + rule] = item.value + break + case 'Number': + if (value === '') value = 1 + let parsed = parseFloat(value) + if (!isNaN(parsed)) value = parsed + result[item.name + rule] = value + break + case 'Boolean': + if (value === 'true') value = true + if (value === 'false') value = false + if (value === '0') value = false + value = !!value + result[item.name + rule] = value + break + case 'Function': + case 'RegExp': + try { + result[item.name + rule] = eval('(' + item.value + ')') // eslint-disable-line no-eval + } catch (e) { + console.warn(`TreeToTemplate ${e.message}: ${item.type} { ${item.name}${rule}: ${item.value} }`) // TODO 2.2 怎么消除异常值? + result[item.name + rule] = item.value + } + break + case 'Object': + if (item.value) { + try { + result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval + } catch (e) { + result[item.name + rule] = item.value + } + } else { + result[item.name + rule] = {} + item.children.forEach((child) => { + parse(child, result[item.name + rule]) + }) + } + break + case 'Array': + if (item.value) { + try { + result[item.name + rule] = eval(`(${item.value})`) // eslint-disable-line no-eval + } catch (e) { + result[item.name + rule] = item.value + } + } else { + result[item.name + rule] = item.children.length ? [{}] : [] + item.children.forEach((child) => { + parse(child, result[item.name + rule][0]) + }) + } + break + } + } + let result = {} + tree.children.forEach((child) => { + parse(child, result) + }) + return result + } + + public static TemplateToData (template) { + // 数据模板 template 中可能含有攻击代码,例如死循环,所以在沙箱中生成最终数据 + // https://nodejs.org/dist/latest-v7.x/docs/api/vm.html + const sandbox = { Mock, template, data: {} } + const script = new vm.Script('data = Mock.mock(template)') + const context = new vm.createContext(sandbox) // eslint-disable-line new-cap + try { + script.runInContext(context, { timeout: 1000 }) // 每次 Mock.mock() 最多执行 1s + // DONE 2.1 __root__ + let data:any = sandbox.data + let keys = Object.keys(data) + if (keys.length === 1 && keys[0] === '__root__') data = data.__root__ + return data + } catch (err) { + console.error(err) + return {} + } + } + + public static ArrayToTreeToTemplate(list) { + let tree = Tree.ArrayToTree(list) + let template = Tree.TreeToTemplate(tree) + return template + } + + public static ArrayToTreeToTemplateToData(list, extra?:any) { + let tree = Tree.ArrayToTree(list) + let template = Tree.TreeToTemplate(tree) + let data + + if (extra) { + // DONE 2.2 支持引用请求参数 + let keys = Object.keys(template).map(item => item.replace(RE_KEY, '$1')) + let extraKeys = _.difference(Object.keys(extra), keys) + let scopedData = Tree.TemplateToData( + Object.assign({}, _.pick(extra, extraKeys), template) + ) + data = _.pick(scopedData, keys) + } else { + data = Tree.TemplateToData(template) + } + + return data + } + + public static ArrayToTreeToTemplateToJSONSchema(list) { + let tree = Tree.ArrayToTree(list) + let template = Tree.TreeToTemplate(tree) + let schema = Mock.toJSONSchema(template) + return schema + } + + // TODO 2.2 执行 JSON.stringify() 序列化时会丢失正则和函数。需要转为字符串或者函数。 + // X Function.protytype.toJSON = Function.protytype.toString + // X RegExp.protytype.toJSON = RegExp.protytype.toString + public static stringifyWithFunctonAndRegExp(json) { + return JSON.stringify(json, (k, v) => { + if (typeof v === 'function') return v.toString() + if (v !== undefined && v !== null && v.exec) return v.toString() + else return v + }, 2) + } + +} \ No newline at end of file diff --git a/src/routes/utils/url.js b/src/routes/utils/url.js deleted file mode 100644 index bc9c999..0000000 --- a/src/routes/utils/url.js +++ /dev/null @@ -1,33 +0,0 @@ -let pathToRegexp = require('path-to-regexp') - -const pkg = {} - -pkg.getRelative = url => { - if (!url || typeof url !== 'string') return null - 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('?')) - } - return url -} - -pkg.urlMatchesPattern = (url, pattern) => { - url = pkg.getRelative(url) - pattern = pkg.getRelative(pattern) - let re = pathToRegexp(pattern) - return re.test(url) -} - -module.exports = pkg diff --git a/src/routes/utils/url.ts b/src/routes/utils/url.ts new file mode 100644 index 0000000..82b9d7e --- /dev/null +++ b/src/routes/utils/url.ts @@ -0,0 +1,33 @@ +let pathToRegexp = require('path-to-regexp') + +export default class pkg { + + public static getRelative = url => { + if (!url || typeof url !== 'string') return null + 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('?')) + } + return url + } + + public static urlMatchesPattern = (url, pattern) => { + url = pkg.getRelative(url) + pattern = pkg.getRelative(pattern) + let re = pathToRegexp(pattern) + return re.test(url) + } + +} \ No newline at end of file diff --git a/scripts/app.js b/src/scripts/app.ts similarity index 77% rename from scripts/app.js rename to src/scripts/app.ts index 0caa1db..b77a0d7 100644 --- a/scripts/app.js +++ b/src/scripts/app.ts @@ -1,12 +1,12 @@ const debug = true -const Koa = require('koa') -const session = require('koa-session') -const logger = require('koa-logger') -const serve = require('koa-static') -const body = require('koa-body') -const cors = require('kcors') -const router = require('../src/routes') -const config = require('../config') +import * as Koa from 'koa' +import * as session from 'koa-session' +import * as logger from 'koa-logger' +import * as serve from 'koa-static' +import * as body from 'koa-body' +import * as cors from 'kcors' +import router from '../routes' +import config from '../config' const app = new Koa() app.counter = { users: {}, mock: 0 } @@ -45,4 +45,4 @@ app.use(serve('test')) app.use(body()) app.use(router.routes()) -module.exports = app +export default app \ No newline at end of file diff --git a/scripts/dev.js b/src/scripts/dev.ts similarity index 88% rename from scripts/dev.js rename to src/scripts/dev.ts index c25a73d..002621e 100644 --- a/scripts/dev.js +++ b/src/scripts/dev.ts @@ -1,7 +1,8 @@ +import config from '../config' +import app from './app' + const start = () => { let execSync = require('child_process').execSync - let app = require('./app') - const config = require('../config') let port = config.serve.port let url = `http://localhost:${port}` // /api.html let open = false @@ -18,3 +19,4 @@ const start = () => { } start() +export {} diff --git a/scripts/init/bo.js b/src/scripts/init/bo.js similarity index 99% rename from scripts/init/bo.js rename to src/scripts/init/bo.js index 6d1094f..ae04989 100644 --- a/scripts/init/bo.js +++ b/src/scripts/init/bo.js @@ -19,7 +19,6 @@ module.exports = { BO_USER_COUNT: 10, BO_USER_FN: () => mock({ id: USER_ID++, - empId: 'bo@natural', fullname: '@cname', email: '@email', password: '@word(6)' diff --git a/scripts/init/delos.js b/src/scripts/init/delos.js similarity index 100% rename from scripts/init/delos.js rename to src/scripts/init/delos.js diff --git a/scripts/init/index.js b/src/scripts/init/index.js similarity index 100% rename from scripts/init/index.js rename to src/scripts/init/index.js diff --git a/scripts/openChrome.applescript b/src/scripts/openChrome.applescript similarity index 100% rename from scripts/openChrome.applescript rename to src/scripts/openChrome.applescript diff --git a/scripts/rap2_delos.sql b/src/scripts/rap2_delos.sql similarity index 100% rename from scripts/rap2_delos.sql rename to src/scripts/rap2_delos.sql diff --git a/scripts/worker.js b/src/scripts/worker.ts similarity index 98% rename from scripts/worker.js rename to src/scripts/worker.ts index 68119ac..ff55c61 100644 --- a/scripts/worker.js +++ b/src/scripts/worker.ts @@ -19,3 +19,5 @@ const start = () => { } start() + +export {} diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000..5cede6e --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,15 @@ +import { PoolOptions } from "sequelize"; +import { ISequelizeConfig } from "sequelize-typescript"; + +declare interface IConfigOptions { + version: string, + serve: { + port: number + }, + keys: string[], + session: { + key: string + }, + keycenter?: string | boolean, + db: ISequelizeConfig +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d32ae8d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "outDir": "dist", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "allowJs": true, + "target": "es6", + "removeComments": true, + "sourceMap": true, + "watch": false, + "baseUrl": "./src", + "rootDir": "./src", + "paths": { + "*": [ + "node_modules/*", + "src/types/*" + ] + } + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..c59fb60 --- /dev/null +++ b/tslint.json @@ -0,0 +1,104 @@ + + { + "rules": { + "no-eval": true, + "forin": true, + "no-any": true, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-for-in-array": false, + "no-invalid-this": true, + "no-string-literal": true, + "no-string-throw": true, + "no-unsafe-finally": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "radix": true, + "switch-default": true, + "triple-equals": [true, "allow-null-check"], + + "adjacent-overload-signatures": true, + "array-type": [true, "array"], + "arrow-parens": false, + "callable-types": true, + "class-name": true, + "comment-format": [true, "check-space", "check-uppercase"], + "interface-name": [true, "always-prefix"], + "jsdoc-format": true, + "max-classes-per-file": [true, 3], + "max-file-line-count": [true, 500], + "max-line-length": [true, 140], + "member-access": true, + "member-ordering": [true, { "order": "fields-first" }], + "new-parens": true, + "no-construct": true, + "no-default-export": true, + "no-inferrable-types": false, + "no-null-keyword": false, + "no-parameter-properties": true, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-var-keyword": true, + "no-var-requires": true, + "object-literal-sort-keys": false, + "one-variable-per-declaration": [true, "ignore-for-loop"], + "only-arrow-functions": false, + "ordered-imports": [true, "case-insensitive"], + "prefer-const": false, + "prefer-for-of": true, + "typedef": [true, "call-signature", "parameter", "property-declaration", "variable-declaration", "member-variable-declaration"], + "unified-signatures": true, + "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], + + "align": [true, "parameters", "statements"], + "curly": true, + "eofline": true, + "import-spacing": true, + "indent": [true, "spaces"], + "linebreak-style": true, + "no-consecutive-blank-lines": true, + "no-trailing-whitespace": true, + "object-literal-key-quotes": [true, "as-needed"], + "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], + "quotemark": [true, "single"], + "semicolon": [true, "always"], + "trailing-comma": [true, {"singleline": "never", "multiline": "always"}], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ], + "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], + + "ban": false, + "cyclomatic-complexity": false, + "file-header": false, + "import-blacklist": false, + "interface-over-type-literal": false, + "no-angle-bracket-type-assertion": false, + "no-empty-interface": false, + "no-inferred-empty-object-type": false, + "no-internal-module": false, + "no-magic-numbers": false, + "no-mergeable-namespace": false, + "no-namespace": false, + "no-reference": true, + "object-literal-shorthand": false, + "space-before-function-paren": false + } + } \ No newline at end of file