adba
Version:
Any DataBase to API
337 lines (336 loc) • 14.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const status_codes_1 = __importDefault(require("./status-codes"));
/**
* @class Controller
*
* @description
* A controller class for handling database queries using Objection.js models.
*/
class Controller {
/**
* @constructor
*
* @param {typeof Model} SqliteModel - The Objection.js model.
* @param {Object} options - Additional options for the controller.
* @param {string[]} [options.searchIn] - Columns to search in by default.
*/
constructor(SqliteModel, options = {}) {
this.Model = SqliteModel;
this.qcolumns = options === null || options === void 0 ? void 0 : options.searchIn;
}
/**
* Finds string type columns in the model's JSON schema.
*
* @param {typeof Model} ModelIn - The model to inspect. Defaults to this.Model.
* @returns {string[]} A list of string type column names.
*/
findTypeString(ModelIn = this.Model) {
const properties = ModelIn.jsonSchema.properties;
const stringFields = [];
for (const key in properties) {
const field = properties[key];
if (field.type === 'string') {
stringFields.push(key);
}
}
return stringFields;
}
/**
* Lists records based on search criteria.
*
* @param {ISearch} dataSearch - The search criteria.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
list(dataSearch, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
const ModelInUse = (queryBuilder === null || queryBuilder === void 0 ? void 0 : queryBuilder._modelClass) || this.Model;
const { filters, orderBy = {}, fields: rawFields, limit: rawLimit, offset: rawOffset, page: rawPage, q } = dataSearch;
let limit;
let offset;
let page;
// Set pagination defaults
if (rawLimit !== false) {
limit = !rawLimit || rawLimit === true ? 20 : rawLimit;
offset = rawOffset || ((rawPage || 0) * limit) || 0;
page = rawPage || Math.trunc(offset / limit) || 0;
}
// Columns to return
const fields = new Set();
const fieldsReq = !rawFields ? [] :
Array.isArray(rawFields)
? rawFields
: rawFields.split(',');
fieldsReq.forEach((f) => fields.add(f.trim()));
if (fields.size) {
fields.add(ModelInUse.tableName + '.id');
const finalFields = Array.from(fields);
queryBuilderIn.select(finalFields);
}
// Omni search
if (typeof q === 'string') {
const tableName = ModelInUse.tableName + '.';
const query = q.replace(/(['\\])/g, '\\$1');
const columns = Array.isArray(this.qcolumns) ? [...this.qcolumns] : this.findTypeString(ModelInUse);
const locateOrder = `WHEN {{column}} = '${query}' THEN 0 WHEN {{column}} LIKE '${query}%' THEN 1 WHEN {{column}} LIKE '%${query}' THEN 4`;
const regexColumn = /\{\{column\}\}/g;
const order = [];
const orWhereClauses = [];
columns.map((column) => {
const orderClause = [
column.includes('.') ? column : tableName + column,
'like', `%${query}%`
];
orWhereClauses.push(orderClause);
const orderString = locateOrder.replace(regexColumn, (column.includes('.') ? column : tableName + column));
order.push(orderString);
});
queryBuilderIn.where((builder) => {
orWhereClauses.forEach(w => builder.orWhere(...w));
});
if (!Object.keys(orderBy).length) {
queryBuilderIn.orderByRaw('CASE ' + order.join(' ') + ' ELSE 3 END');
}
}
const entriesOrderBy = Object.keys(orderBy);
if (entriesOrderBy.length) {
const [col, dir] = entriesOrderBy.pop();
if (col.includes('.'))
queryBuilderIn.orderBy(col, dir);
else
queryBuilderIn.orderBy(`${ModelInUse.tableName}.${col}`, dir);
}
// Filtering by columns
if (typeof filters === 'object') {
queryBuilderIn.where((builder) => {
Object.keys(filters).forEach((col) => {
const tableColumn = col.includes('.') ? col : ModelInUse.tableName + '.' + col;
const search = filters[col];
if (Array.isArray(search)) {
const where = ModelInUse.jsonSchema
.properties[col].type === 'string' ||
search.length > 2 ? 'whereIn' : 'whereBetween';
builder[where](tableColumn, search);
}
else if (ModelInUse.jsonSchema.properties[col]) {
if (ModelInUse.jsonSchema.properties[col].type === 'string') {
builder.where(tableColumn, 'like', '%' + search + '%');
}
else if (['number', 'integer', 'float', 'decimal']
.includes(ModelInUse.jsonSchema.properties[col].type)) {
builder.where(tableColumn, Number(search));
}
else {
builder.where(tableColumn, search);
}
}
else {
builder.where(tableColumn, 'like', '%' + search + '%');
}
});
});
}
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
let response;
if (!!limit) {
response = queryBuilderIn.page(page, limit)
.then((r) => ({
total: r.total,
data: r.results,
limit,
offset,
page
}));
}
else {
response = queryBuilderIn.then((data) => ({
total: data.length,
data,
limit,
offset: offset || 0,
page
}));
}
return response
.then((resp) => this.successMerge(resp))
.catch((error) => this.error(error));
}
/**
* Selects a record by its ID.
* @param {IIds} param0 - The ID or IDs to select.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
selectById({ id }, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.findById(id);
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((row) => {
if (!row)
return this.error(id, 404, 0);
return this.success(row);
})
.catch((error) => this.error(error));
}
/**
* Selects one active record based on given criteria.
* @param {object} find - Criteria to find the record.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
selectOneActive(find, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.findOne(Object.assign(Object.assign({}, find), { active: true }));
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((row) => {
if (!row)
return this.error(find, 404, 0);
return this.success(row);
})
.catch((error) => this.error(error));
}
/**
* Selects one record based on given criteria without considering active state.
* @param {object} find - Criteria to find the record.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
selectOne(find, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.findOne(find);
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((row) => {
if (!row)
return this.error(find, 404, 0);
return this.success(row);
})
.catch((error) => this.error(error));
}
/**
* Inserts one or multiple records.
*
* @param {object | object[]} data - The data to insert.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
insert(data, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.insertGraph(data, { allowRefs: true });
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((resp) => this.success(resp))
.catch((error) => this.error(error));
}
/**
* Updates records using an upsert operation.
*
* @param {object | object[]} data - The data for upsert.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
update(data, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.upsertGraph(data, { allowRefs: true });
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((resp) => this.success(resp))
.catch((error) => this.error(error));
}
/**
* Deletes records based on where clause.
*
* @param {WhereMethod<any>} whereData - Criteria for delete operation.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
deleteWhere(whereData, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.delete().where(whereData);
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((resp) => this.success(resp))
.catch((error) => this.error(error));
}
/**
* Deletes records by ID(s).
*
* @param {IIds} param0 - The ID or IDs of records to delete.
* @param {QueryBuilderType<any>} [queryBuilder] - Optional query builder instance.
* @returns {Promise<object>} The promise of the resulting data.
*/
delete({ id, ids }, queryBuilder) {
const queryBuilderIn = queryBuilder ? queryBuilder.clone() : this.Model.query();
queryBuilderIn.clear(true);
queryBuilderIn.delete().whereIn('id', [id, ids].flat().filter(Boolean));
if (queryBuilder)
queryBuilderIn.copyFrom(queryBuilder, true);
return queryBuilderIn
.then((resp) => this.success(resp))
.catch((error) => this.error(error));
}
/**
* Formats a successful response object.
*
* @param {any} data - The data to return in the response.
* @param {number} [status=200] - The HTTP status code.
* @param {number} [code=0] - Additional status code.
* @returns {object} The success response object.
*/
success(data, status = 200, code = 0) {
const toReturn = Object.assign({ error: false, success: true }, (0, status_codes_1.default)(status, code), { data });
return toReturn;
}
/**
* Merges additional data into a successful response object.
*
* @param {any} data - The data to return in the response.
* @param {number} [status=200] - The HTTP status code.
* @param {number} [code=0] - Additional status code.
* @returns {object} The merged success response object.
*/
successMerge(data, status = 200, code = 0) {
const toReturn = Object.assign({ error: false, success: true }, (0, status_codes_1.default)(status, code), data);
return toReturn;
}
/**
* Formats an error response object.
*
* @param {any} errorObj - The error object or message.
* @param {number} [status=500] - The HTTP status code.
* @param {number} [code=0] - Additional status code.
* @returns {object} The error response object.
*/
error(errorObj, status = 500, code = 0) {
let toReturn;
if (errorObj instanceof Error) {
console.error(errorObj);
toReturn = Object.assign({ error: true, success: false, }, (0, status_codes_1.default)(500, 0), { data: errorObj.message });
}
else {
toReturn = Object.assign({ error: true, success: false }, (0, status_codes_1.default)(status, code), { data: errorObj });
}
return toReturn;
}
}
exports.default = Controller;