UNPKG

dobo-restproxy

Version:
236 lines (216 loc) 9.72 kB
const authTypes = ['basic', 'apiKey', 'jwt'] const methods = { find: 'GET', get: 'GET', create: 'POST', update: 'PUT', remove: 'DELETE', aggregate: 'GET', histogram: 'GET' } const optsKeys = { qs: ['query', 'page', 'limit', 'sort'], response: ['data', 'page', 'count'] } async function restproxyAdapterFactory () { const { join } = this.app.bajo const { DoboAdapter } = this.app.baseClass const { cloneDeep, isString, trimEnd, isPlainObject, trimStart, get, set } = this.app.lib._ class DoboRestproxyAdapter extends DoboAdapter { static optsKeys = cloneDeep(optsKeys) static authTypes = cloneDeep(authTypes) static methods = cloneDeep(methods) constructor (plugin, name, options) { super(plugin, name, options) this.idField.name = 'id' this.memory = true } async sanitizeConnection (item) { await super.sanitizeConnection(item) item.port = item.port ?? 6379 item.host = item.host ?? '127.0.0.1' item.database = item.database ?? '0' if (isString(item.url)) { item.url = { base: trimEnd(item.url), find: 'GET:{modelName}', get: 'GET:{modelName}/{id}', create: 'POST:{modelName}', update: 'PUT:{modelName}/{id}', remove: 'DELETE:{modelName}/{id}', aggregate: 'GET:{modelName}/stat/aggregate', histogaram: 'GET:{modelName}/stat/histogram' } } else if (isPlainObject((item.url))) { if (!item.url.base) throw this.plugin.error('baseUrlMissing') item.url.base = trimEnd(item.url.base.trim(), '/') for (const method in this.constructor.methods) { if (!item.url[method]) continue let [m, u] = item.url[method].split(':').map(item => item.trim()) if (!u) { u = m m = this.constructor.methods[method] } u = trimStart(u, '/') if (!u.includes('{modelName}')) throw this.plugin.error('urlPattern%s', method) if (['get', 'update', 'remove'].includes(method) && !u.includes('{id}')) throw this.plugin.error('urlIdPattern%s', method) item.url[method] = `${m}:${u}` } } item.auth = item.auth ?? 'apiKey' if (item.auth !== false) { const types = this.constructor.authTypes if (!types.includes(item.auth)) throw this.plugin.error('onlySupportThese%s', join(types)) switch (item.auth) { case 'apiKey': if (!item.apiKey) throw this.plugin.error('isMissing%s', this.t('field.apiKey')); break case 'jwt': if (!item.jwt) throw this.plugin.error('isMissing%s', this.t('field.jwt')); break case 'basic': if (!item.username) throw this.plugin.error('isMissing%s', this.t('field.username')) if (!item.password) throw this.plugin.error('isMissing%s', this.t('field.password')) break } } item.fieldsMap = item.fieldsMap ?? {} if (Array.isArray(item.fieldsMap)) { const map = {} for (const f of item.fieldsMap) { const [field, nfield] = f.split(':').map(f => f.trim()) if (!nfield || (nfield === field)) continue map[field] = nfield } item.fieldsMap = map } for (const type in this.constructor.optsKeys) { for (const key of this.constructor.optsKeys[type]) { const [def, org] = key.split(':') const realKey = `${type}Key.${def}` const val = get(item, realKey, org ?? def) set(item, realKey, val) } } } async _transform (data, model, reverse) { const { callHandler } = this.app.bajo const conn = model.connection const mapFields = (rec, reverse) => { const { get, invert } = this.app.lib._ const fm = invert(conn.options.fieldsMap) const newRec = {} for (const key in rec) { const nkey = get(reverse ? fm.fieldsMap : conn.options.fieldsMap, key, key) newRec[nkey] = rec[key] } return newRec } const arr = Array.isArray(data) if (!arr) data = [data] for (const i in data) { let d = data[i] if (conn.options.transformer) d = await callHandler(this, conn.options.transformer, d, model.name) data[i] = mapFields(d, reverse) } return arr ? data : data[0] } async _prepFetch (action, model, idOrFilter, bodyOrParams, options = {}) { const { callHandler } = this.app.bajo const { replaceRegexInJson } = this.app.dobo const { pick, cloneDeep, invert, has, isEmpty } = this.app.lib._ const { isSet } = this.app.lib.aneka const { options: conn } = model.connection const opts = pick(conn, ['qsKeys', 'responseKeys']) const ext = cloneDeep(conn.fetchExtraOpts ?? {}) if (!conn.url[action]) throw this.plugin.error('methodIsDisabled%s%s', action, model.name) let [method, url] = conn.url[action].split(':') let name = model.name if (conn.modelResolver) name = await callHandler(conn.modelResolver, name) url = `${conn.url.base}/${url}`.replace('{modelName}', name) if (!isPlainObject(idOrFilter)) url = url.replace('{id}', idOrFilter) else { if (!isEmpty(idOrFilter.orgQuery)) idOrFilter.query = idOrFilter.orgQuery if (!isEmpty(idOrFilter.orgSearch)) idOrFilter.search = idOrFilter.orgSearch if (bodyOrParams) { const items = await this._transform(bodyOrParams, model, true) if (['POST', 'PUT'].includes(method)) opts.body = items else opts.params = items } } opts.method = method.toLowerCase() opts.headers = opts.headers ?? {} opts.params = opts.params ?? {} switch (conn.auth) { case 'basic': opts.auth = { username: conn.username, password: conn.password }; break case 'apiKey': opts.headers.Authorization = `Bearer ${conn.apiKey}`; break case 'jwt': opts.headers.Authorization = `Bearer ${conn.jwt}`; break } const dataKey = get(conn.responseKey, 'data') const oldDataKey = get(conn.responseKey, 'oldData') if (isPlainObject(idOrFilter)) { // id is actually filter const fm = invert(conn.fieldsMap) const newSort = {} for (const s in idOrFilter.sort) { newSort[fm[s] ?? s] = idOrFilter.sort[s] } opts.params[conn.qsKey.sort] = newSort for (const k in conn.qsKey) { if (k === 'sort') continue if (has(idOrFilter, k)) { const val = isPlainObject(idOrFilter[k]) ? replaceRegexInJson(idOrFilter[k]) : idOrFilter[k] if (!isSet(val)) continue opts.params[conn.qsKey[k]] = val } if (has(bodyOrParams, k)) { const val = bodyOrParams[k] if (!isSet(val)) continue opts.params[conn.qsKey[k]] = val } } } return { url, opts, ext, dataKey, oldDataKey } } async createRecord (model, body = {}, options = {}) { const { isEmpty } = this.app.lib._ const { url, opts, ext, dataKey } = await this._prepFetch('create', model, undefined, body, options) const resp = await this.plugin.fetch(url, opts, ext) if (options.noResult) return const data = await this._transform(isEmpty(dataKey) ? resp : resp[dataKey], model) return { data } } async getRecord (model, id, options = {}) { const { isEmpty } = this.app.lib._ const { url, opts, ext, dataKey } = await this._prepFetch('get', model, id, null, options) const resp = await this.plugin.fetch(url, opts, ext) const data = await this._transform(isEmpty(dataKey) ? resp : resp[dataKey], model) return { data } } async updateRecord (model, id, body = {}, options = {}) { const { isEmpty } = this.app.lib._ const { url, opts, ext, dataKey, oldDataKey } = await this._prepFetch('update', model, id, body, options) const resp = await this.plugin.fetch(url, opts, ext) const data = await this._transform(isEmpty(dataKey) ? resp : resp[dataKey], model) const oldData = await this._transform(isEmpty(oldDataKey) ? resp : resp[oldDataKey], model) return { data, oldData } } async removeRecord (model, id, options = {}) { const { isEmpty } = this.app.lib._ const { url, opts, ext, oldDataKey } = await this._prepFetch('remove', model, id, null, options) const resp = await this.plugin.fetch(url, opts, ext) const oldData = await this._transform(isEmpty(oldDataKey) ? resp : resp[oldDataKey], model) return { oldData } } async findRecord (model, filter = {}, options = {}) { const { isEmpty } = this.app.lib._ const { url, opts, ext, dataKey } = await this._prepFetch('find', model, filter, null, options) const resp = await this.plugin.fetch(url, opts, ext) const data = await this._transform(isEmpty(dataKey) ? resp : resp[dataKey], model) const result = { data, page: resp[model.connection.options.responseKey.page] ?? filter.page, limit: resp[model.connection.options.responseKey.limit] ?? filter.limit } for (const key of ['count', 'pages']) { if (resp[model.connection.options.responseKey[key]]) result[key] = resp[model.connection.options.responseKey[key]] } return result } async countRecord (model, filter = {}, options = {}) { const resp = this.findRecord(model, filter, options) return { data: resp.count } } } this.app.baseClass.DoboRestproxyAdapter = DoboRestproxyAdapter return DoboRestproxyAdapter } export default restproxyAdapterFactory