/**
* @module Helper
*/
/**
* @external DoboModel
* @see {@link https://ardhi.github.io/dobo/DoboModel.html|DoboModel}
*/
/**
* @external TFilter
* @see {@link https://ardhi.github.io/dobo/DoboModel.html#.TFilter|TFilter}
*/
/**
* @external TOptions
* @see {@link https://ardhi.github.io/dobo/DoboModel.html#.TOptions|TOptions}
*/
/**
* Type of actions that can be performed on records in the database.
* @typedef TActions
* @type {Object}
* @property {number} 0='countRecord' - Count the number of records
* @property {number} 1='createAggregate' - Create an aggregate record
* @property {number} 2='createHistogram' - Create a histogram record
* @property {number} 3='createRecord' - Create a new record
* @property {number} 4='findAllRecord' - Find all records
* @property {number} 5='findOneRecord' - Find one record
* @property {number} 6='findRecord' - Find records
* @property {number} 7='getRecord' - Get a record
* @property {number} 8='removeRecord' - Remove a record
* @property {number} 9='updateRecord' - Update a record
*/
export const actions = ['countRecord', 'createAggregate', 'createHistogram', 'createRecord', 'findAllRecord', 'findOneRecord', 'findRecord', 'getRecord', 'removeRecord', 'updateRecord']
/**
* Prepare CRUD operation parameters.
* @async
* @param {Object} [options={}] - Options for preparing CRUD operation
* @param {string|external:DoboModel} options.model - The model to operate on
* @param {string|number} [options.id] - The record ID
* @param {Object} [options.req] - The request object
* @param {Object} [options.reply] - The reply object
* @param {Object} [options.transaction] - The transaction object
* @param {Object} [options.options] - Additional options
* @param {Array} [options.args] - Additional arguments
* @returns {Object} Prepared CRUD parameters
*/
export async function prepCrud (options = {}) {
let { model, id, req, reply, transaction, options: _options = {}, args } = options
const { isSet } = this.app.lib.aneka
const { importModule } = this.app.bajo
const { parseFilter } = this.app.waibu
const { pascalCase } = this.app.lib.aneka
const { cloneDeep, has, isString, omit } = this.app.lib._
const { parseObject } = this.app.lib
const { buildFilterQuery, buildFilterSearch } = await importModule('dobo:/lib/factory/model/helper.js', { asDefaultImport: false })
const cfgWeb = this.app.waibu.getConfig()
const opts = cloneDeep(omit(_options, ['trx']))
opts.throwNotFound = true
if (opts.suppressError === true) opts.suppressError = actions
else if (isString(opts.suppressError)) opts.suppressError = [opts.suppressError]
else opts.suppressError = opts.suppressError ?? []
const params = this.getParams(req, ...args)
for (const k of ['count', 'fields']) {
opts[k] = opts[k] ?? params[k]
}
if (has(_options, 'count')) opts.count = _options.count
opts.dataOnly = opts.dataOnly ?? false
opts.req = req
opts.reply = reply
opts.trx = opts.trx ?? _options.trx ?? transaction ?? true
let { attachment, stats, mimeType } = opts
attachment = attachment ?? req.query.attachment
stats = stats ?? req.query.stats
mimeType = mimeType ?? req.query.mimeType
let refs = []
const headers = parseObject(req.headers, { parseValue: true })
if (isSet(headers['x-count'])) _options.count = headers['x-count']
if (isSet(headers['x-refs'])) refs = headers['x-refs']
if (typeof refs === 'string' && !['*', 'all'].includes(refs)) refs = [refs]
if (refs.length > 0) _options.refs = refs
const recId = id ?? params.id ?? req.query.id
let mdl = model
if (!model || isString(model)) {
model = model ?? pascalCase(params.model)
mdl = this.app.dobo.getModel(model)
}
opts.bboxLatField = req.query[cfgWeb.qsKey.bboxLatField]
opts.bboxLngField = req.query[cfgWeb.qsKey.bboxLngField]
const filter = parseFilter(req)
filter.query = buildFilterQuery.call(mdl, filter)
filter.search = buildFilterSearch.call(mdl, filter)
if (_options.query) filter.query = cloneDeep(_options.query)
if (_options.search) filter.search = cloneDeep(_options.search)
if (_options.limit) filter.limit = _options.limit
if (_options.sort) filter.sort = _options.sort
if (_options.page) filter.page = _options.page
if (opts.fields && opts.fields.length > 0 && mdl.hasProperty('_immutable')) opts.fields.push('_immutable')
const input = cloneDeep(req.body) ?? {}
return { model: mdl, input, recId, opts, filter, attachment, stats, mimeType }
}
/**
* Get a single record from the database based on the provided model, ID, filter, and options.
* @async
* @param {external:DoboModel} model - The model to query
* @param {string|number} id - The ID of the record to retrieve
* @param {external:TFilter} [filter={}] - The filter criteria for the query
* @param {external:TOptions} [options={}] - Additional options for the query
* @returns {Promise<Object>} The retrieved record
*/
export async function getOneRecord (model, id, filter = {}, options = {}) {
const { cloneDeep, pick, isEmpty } = this.app.lib._
let query = cloneDeep(filter.query || {})
query = { $and: [query, { id }] }
const opts = pick(options, ['forceNoHidden', 'trx', 'req', 'refs', 'fmt'])
opts.dataOnly = false
const data = await model.findOneRecord({ query }, opts)
if (isEmpty(data.data) && options.throwNotFound) throw this.error('_notFound')
return data
}
/**
* Process a database operation with error suppression and transaction handling.
* @async
* @param {Object} [opts={}] - Options for processing the handler
* @param {string} opts.action - The action to perform (e.g., 'createRecord', 'updateRecord')
* @param {external:DoboModel} opts.model - The model to operate on
* @param {Function} opts.handler - The handler function to execute
* @param {external:TOptions} [opts.options] - Additional options for the handler
*/
export async function processHandler (opts = {}) {
const { action, model, handler, options } = opts
function suppressedReturn (err) {
this.log.error(err)
if (action === 'countRecord') return options.dataOnly ? 0 : { data: 0, warnings: [options.req.t('supppressedError')] }
if (['createRecord', 'getRecord'].includes(action)) return options.dataOnly ? {} : { data: {}, warnings: [options.req.t('supppressedError')] }
if (action === 'removeRecord') return options.dataOnly ? {} : { oldData: {}, warnings: [options.req.t('supppressedError')] }
if (action === 'updateRecord') return options.dataOnly ? {} : { data: {}, oldData: {}, warnings: [options.req.t('supppressedError')] }
return options.dataOnly ? [] : { data: [], count: 0, page: 1, warnings: [options.req.t('supppressedError')] }
}
try {
if (options.trx === true && ['createRecord', 'updateRecord', 'upsertRecord', 'removeRecord'].includes(action)) {
return await model.transaction(handler, action, options)
}
return await handler()
} catch (err) {
if (options.suppressError.includes(action)) return suppressedReturn.call(this, err)
throw err
}
}