@hosoft/restful-api-framework
Version:
Base framework of the headless cms HoServer provided by http://helloreact.cn
335 lines (278 loc) • 11.2 kB
JavaScript
/* eslint-disable prefer-regex-literals */
/**
* HoServer API Server Ver 2.0
* Copyright http://hos.helloreact.cn
*
* create: 2019/8/6
**/
const _ = require('lodash')
const pluralize = require('pluralize')
const BaseHelper = require('../helpers/base-helper')
const CacheManager = require('../memory-cache/cache-manager')
const Constants = require('../constants/constants')
const DefaultApiHandler = require('../default-api')
const rReturnFunc = new RegExp(/return.*?Service\.(.*?)\(/)
const rFunc = new RegExp(/Service\.(.*?)\(/)
/**
* Route proxy
*/
class RouteProxy {
constructor(container, apiInfo, method, path, disName, func, options) {
this.container = container
this.modelCache = CacheManager.getInstance('memory')
const api = {}
api.method = method
api.path = path
api.dis_name = disName
_.merge(api, options)
if (apiInfo) {
_.merge(api, apiInfo)
}
if (!api.name) {
const functionCode = func.toString ? func.toString() : ''
let match = functionCode.match(rReturnFunc)
if (match && match[1]) {
api.name = match[1]
} else {
match = functionCode.match(rFunc)
if (match && match[1]) {
api.name = match[1]
}
}
}
if (!api.type) {
if (api.model) {
const model = BaseHelper.getModel(api.model)
api.type = model ? model.meta.type || 3 : 3 // by default user defined
} else {
api.type = 3
}
}
if (!api.in_params) {
api.in_params = []
}
if (!api.out_fields) {
api.out_fields = []
}
this.api = api
this.func = func
this.processOptions(options)
}
beforeDbProcess(func) {
this.beforeQueryFunc = func
this.container.setHook('beforeDbProcess', func, this.api.path, this.api.method)
return this
}
beforeProcess(func) {
this.container.setHook('beforeProcess', func, this.api.path, this.api.method)
return this
}
afterProcess(func) {
this.container.setHook('afterProcess', func, this.api.path, this.api.method)
return this
}
outFields(addFields, removeFields) {
if (!(this.api.out_fields && this.api.out_fields.length > 0)) {
logger.error("api hasn't set out_fields: " + this.api.name)
return this
}
if (addFields) {
addFields = typeof addFields === 'string' ? addFields.split(/[\s|,,]/gi) : addFields
if (!(addFields instanceof Array)) {
throw new Error('addFields param type error')
}
let model = null
let defOutFields = null
if (this.api.model) {
defOutFields = []
model = BaseHelper.getModel(this.api.model)
DefaultApiHandler.getModelOutFields(defOutFields, model.properties, '', true)
}
for (const field of addFields) {
if (this.api.out_fields.findIndex((f) => f.name === field) > -1) {
continue
}
let index = defOutFields.findIndex((f) => f.name === field)
if (index > -1) {
this.api.out_fields.push({ ...defOutFields[index] })
const relField = field + '_rel'
index = defOutFields.findIndex((f) => f.name === relField)
if (index > -1) {
this.api.out_fields.push({ ...defOutFields[index] })
}
} else {
this.api.out_fields.push({
name: field,
description: ''
})
}
}
}
if (removeFields) {
removeFields = typeof removeFields === 'string' ? removeFields.split(/[|,,]/gi) : removeFields
if (!(removeFields instanceof Array)) {
throw new Error('removeFields param type error')
}
for (const field of removeFields) {
let index = this.api.out_fields.findIndex((f) => f.name === field)
if (index > -1) {
this.api.out_fields.splice(index, 1)
const relField = field + '_rel'
index = this.api.out_fields.findIndex((f) => f.name === relField)
if (index > -1) {
this.api.out_fields.splice(index, 1)
}
}
}
}
return this
}
processOptions(options) {
if (!options) {
return
}
if (options.permissions && typeof options.permissions === 'string') {
options.permissions = [options.permissions]
}
if (options.cache && typeof options.cache === 'boolean') {
options.cache = { enabled: options.cache }
}
if (options.beforeDbProcess) {
this.beforeDbProcess(options.beforeDbProcess)
}
if (options.beforeProcess) {
this.beforeProcess(options.beforeProcess)
}
if (options.afterProcess) {
this.afterProcess(options.afterProcess)
}
if (options.outFields) {
this.outFields(options.outFields)
}
}
async fillCategoryName() {
const api = this.api
const categoryNameDict = await BaseHelper.getSystemDict('sys_category')
const modelNameMap = await this._getModelNameMap()
const modelRouteMap = await this._getModelRouteMap()
const getCategoryDisName = (categoryName, mustExists) => {
if (categoryName === '_default') {
return tf('defaultCategory')
}
let disName = categoryNameDict[categoryName]
if (!disName) {
const model = modelNameMap[categoryName]
disName = model ? model.disName : ''
}
return mustExists ? disName || categoryName : disName
}
let mainCategory = api.category
let secondCategory = ''
let routePath = ''
const pos = api.path.indexOf(Constants.API_PREFIX)
if (pos > -1) {
routePath = api.path.substr(pos + Constants.API_PREFIX.length + 1)
if (routePath.startsWith('/')) {
routePath = routePath.substr(1)
}
}
const parts = routePath.split('/')
if (parts.length > 2) {
routePath = `${parts[0]}/${parts[1]}`
}
// check model
if (!mainCategory) {
if (api.model) {
const model = BaseHelper.getModel(api.model)
mainCategory = model ? model.meta.category_name : ''
secondCategory = api.model.split('.')[0]
} else if (routePath) {
const modelName = modelRouteMap[routePath]
if (modelName) {
const model = BaseHelper.getModel(modelName)
mainCategory = model ? model.meta.category_name : ''
secondCategory = modelName.split('.')[0]
}
}
}
if (routePath) {
if (parts.length > 0) {
// const part1 = pluralize(parts[0], 1) // sns has problem
const part0 = categoryNameDict[parts[0]] ? parts[0] : pluralize(parts[0], 1)
const part1 =
parts.length === 1 ? (categoryNameDict[parts[0]] ? pluralize(parts[0], 1) : parts[0]) : parts[1]
// ex: /api/v1/payment/prepay/wx, make same category as Payment model
if (!mainCategory && modelNameMap[part1]) {
const routeModel = BaseHelper.getModel(modelNameMap[part1].name)
mainCategory = routeModel ? routeModel.meta.category_name : ''
}
if (mainCategory) {
api.main_cat = mainCategory
api.main_catname = getCategoryDisName(api.main_cat, true)
} else {
api.main_cat = part0 || '_default'
api.main_catname = getCategoryDisName(part0, true)
}
if (!secondCategory) {
if (parts.length > 1) {
secondCategory = categoryNameDict[parts[1]] ? pluralize(parts[1], 1) : parts[1]
} else {
secondCategory = part1
}
}
api.second_catname = getCategoryDisName(secondCategory || '_default')
// ex: app/active_code, when active_code is not in dict
if (!api.second_catname) {
api.second_catname = tf('defaultCategory')
api.second_cat = '_default'
} else {
api.second_cat = secondCategory
}
api.category_name = api.main_cat + (api.second_cat ? '/' + api.second_cat : '')
}
}
if (!api.category_name) {
api.main_cat = '_default'
api.main_catname = tf('defaultCategory')
api.second_cat = '_default'
api.second_catname = tf('defaultCategory')
api.category_name = '_default'
}
}
async _getModelRouteMap() {
let modelRouteMap = await this.modelCache.get('ModelRouteMap')
if (!modelRouteMap) {
modelRouteMap = {}
const models = BaseHelper.getContainer().getAllModels()
const keys = _.keys(models)
for (const modelName of keys) {
const routePath = models[modelName].getRoutePath()
modelRouteMap[routePath.routeName] = modelName
}
await this.modelCache.set('ModelRouteMap', modelRouteMap)
}
return modelRouteMap
}
async _getModelNameMap() {
let modelNameMap = await this.modelCache.get('ModelNameMap')
if (!modelNameMap) {
modelNameMap = {}
const models = BaseHelper.getContainer().getAllModels()
const modelNames = _.keys(models)
for (const modelName of modelNames) {
const dictItem = { name: modelName, disName: models[modelName].meta.dis_name }
modelNameMap[modelName] = dictItem
modelNameMap[modelName.toLowerCase()] = dictItem
if (pluralize.isPlural(modelName)) {
modelNameMap[pluralize(modelName, 1)] = dictItem
modelNameMap[pluralize(modelName.toLowerCase(), 1)] = dictItem
} else {
modelNameMap[pluralize(modelName.toLowerCase())] = dictItem
}
}
await this.modelCache.set('ModelNameMap', modelNameMap)
}
return modelNameMap
}
}
module.exports = RouteProxy