-const HttpError = require('http-errors');
+/**
+ * @file Base model class for database operations
+ */
+
+const HttpError = require( 'http-errors' );
+
/**
* Custom error for not found records
* @extends HttpError
- * @property {string} name - Error name
*/
class NotFoundError extends HttpError {
/**
* @param {string} message - Error message
*/
- constructor(message) {
+ constructor( message ) {
super( 404, message );
this.name = 'NotFoundError';
- }
+ };
}
/**
/**
* @param {string} message - Error message
*/
- constructor(message) {
- super(400, message);
+ constructor( message ) {
+ super( 400, message );
this.name = 'ValidationError';
- }
+ };
}
-// noinspection DuplicatedCode
/**
* Base model class for database operations
*/
* Create a model instance
* @param {Object} [props] - Properties to initialize the model
*/
- constructor(props) {
- props && Object.keys(props).forEach(c => { this[c] = props[c]; });
+ constructor( props ) {
+ props && Object.keys( props ).forEach( c => {
+ this[c] = props[c];
+ } );
/** @type {string} Database table name */
this.table = '';
/** @type {string[]} Allowed columns for queries */
/** @type {string|undefined} Default ORDER BY clause */
this.default_order_by = undefined;
/** @type {Function} Function to instantiate a model */
- this.instance = _props => new Model(_props);
+ this.instance = _props => new Model( _props );
/** @type {string|undefined} GROUP BY clause */
this.group_by = undefined;
}
* @param {string} prepend - Table alias
* @returns {string} WHERE clause
*/
- where_clause(keys, prepend) {
+ where_clause( keys, prepend ) {
if (!keys?.length) return '';
- return `WHERE ${keys.map((k, index) => `${prepend}.${k} = $${index + 1}`).join(' AND ')}`;
- }
+ return `WHERE ${ keys.map( ( k, index ) => `${ prepend }.${ k } = $${ index + 1 }` ).join( ' AND ' ) }`;
+ };
/**
* Filter WHERE object to valid columns
* @param {string[]} default_columns - Allowed columns
* @returns {{keys: string[], values: any[]}} Keys and values for the query
*/
- build_where(where, default_columns) {
+ build_where( where, default_columns ) {
const keys = [];
const values = [];
if (where) {
- Object.keys(where).forEach(k => {
- if (default_columns.includes(k)) {
- keys.push(k);
- values.push(where[k]);
+ Object.keys( where ).forEach( k => {
+ if (default_columns.includes( k )) {
+ keys.push( k );
+ values.push( where[k] );
}
- });
+ } );
}
return { keys, values };
- }
+ };
/**
* Find one record
* @param {string[]} [excludes] - Fields to exclude from result
* @returns {Promise<Object|null>} Found record or null
*/
- async find_one(where, excludes = []) {
- const { keys, values } = this.build_where(where, this.default_columns);
+ async find_one( where, excludes = [] ) {
+ const { keys, values } = this.build_where( where, this.default_columns );
const result = await phsdb.query(
- `${this.base_query} ${this.where_clause(keys, this.prepend)} ${this.group_by ? this.group_by : ''}`,
+ `${ this.base_query } ${ this.where_clause( keys, this.prepend ) } ${ this.group_by ? this.group_by : '' }`,
values,
- { plain: true }
+ { plain:true }
);
if (!result) return null;
- const found = this.instance(result);
- excludes?.forEach(e => delete found[e]);
+ const found = this.instance( result );
+ excludes?.forEach( e => delete found[e] );
return found;
- }
+ };
- // noinspection JSUnusedGlobalSymbols
/**
* Find one record (raw data)
* @param {Object} where - Conditions for the WHERE clause
* @param {string[]} [excludes] - Fields to exclude from result
* @returns {Promise<Object|null>} Found record or null
*/
- async find_one_simple(where, excludes = []) {
- const { keys, values } = this.build_where(where, this.default_columns);
+ async find_one_simple( where, excludes = [] ) {
+ const { keys, values } = this.build_where( where, this.default_columns );
const result = await phsdb.query(
- `${this.base_query} ${this.where_clause(keys, this.prepend)} ${this.group_by ? this.group_by : ''}`,
+ `${ this.base_query } ${ this.where_clause( keys, this.prepend ) } ${ this.group_by ? this.group_by : '' }`,
values,
- { plain: true }
+ { plain:true }
);
if (!result) return null;
- excludes?.forEach(e => delete result[e]);
+ excludes?.forEach( e => delete result[e] );
return result;
- }
+ };
/**
* Get base list query
* @returns {string} Base list query
*/
get_base_list_query() {
- return `${this.base_list_query}`;
- }
+ return `${ this.base_list_query }`;
+ };
/**
* Find many records
* @param {number} [offset=0] - Number of records to skip
* @returns {Promise<Object[]>} Array of records
*/
- async find_many(where, excludes = [], order_by = null, limit = 100, offset = 0) {
- const { keys, values } = this.build_where(where, this.default_columns);
- values.push(limit, offset);
+ async find_many( where, excludes = [], order_by = null, limit = 100, offset = 0 ) {
+ const { keys, values } = this.build_where( where, this.default_columns );
+ values.push( limit, offset );
const results = await phsdb.query(
`
- ${this.get_base_list_query()}
- ${this.where_clause(keys, this.prepend)}
- ${this.group_by ? this.group_by : ''}
- ${order_by ? `ORDER BY ${order_by.name} ${order_by.direction ?? 'asc'}` : this.default_order_by ?? ''}
- LIMIT $${values.length - 1} OFFSET $${values.length}
+ ${ this.get_base_list_query() }
+ ${ this.where_clause( keys, this.prepend ) }
+ ${ this.group_by ? this.group_by : '' }
+ ${ order_by ? `ORDER BY ${ order_by.name } ${ order_by.direction ?? 'asc' }` : this.default_order_by ?? '' }
+ LIMIT $${ values.length - 1 } OFFSET $${ values.length }
`,
values
);
const res = [];
for (const result of results) {
- const found = this.instance(result);
- excludes?.forEach(e => delete found[e]);
- res.push(found);
+ const found = this.instance( result );
+ excludes?.forEach( e => delete found[e] );
+ res.push( found );
}
return res;
- }
+ };
- // noinspection JSUnusedGlobalSymbols
/**
* Find many records (raw data)
* @param {Object} [where] - Conditions for the WHERE clause
* @param {number} [offset=0] - Number of records to skip
* @returns {Promise<Object[]>} Array of records
*/
- async find_many_lite(where, excludes = [], order_by = null, limit = 100, offset = 0) {
- const { keys, values } = this.build_where(where, this.default_columns);
- values.push(limit, offset);
+ async find_many_lite( where, excludes = [], order_by = null, limit = 100, offset = 0 ) {
+ const { keys, values } = this.build_where( where, this.default_columns );
+ values.push( limit, offset );
const results = await phsdb.query(
`
- ${this.get_base_list_query()}
- ${this.where_clause(keys, this.prepend)}
- ${this.group_by ? this.group_by : ''}
- ${order_by ? `ORDER BY ${order_by.name} ${order_by.direction ?? 'asc'}` : this.default_order_by ?? ''}
- LIMIT $${values.length - 1} OFFSET $${values.length}
+ ${ this.get_base_list_query() }
+ ${ this.where_clause( keys, this.prepend ) }
+ ${ this.group_by ? this.group_by : '' }
+ ${ order_by ? `ORDER BY ${ order_by.name } ${ order_by.direction ?? 'asc' }` : this.default_order_by ?? '' }
+ LIMIT $${ values.length - 1 } OFFSET $${ values.length }
`,
values
);
const res = [];
for (const result of results) {
const data_values = {};
- Object.keys(result).forEach(k => data_values[k] = result[k]);
- excludes?.forEach(e => delete data_values[e]);
- res.push(data_values);
+ Object.keys( result ).forEach( k => data_values[k] = result[k] );
+ excludes?.forEach( e => delete data_values[e] );
+ res.push( data_values );
}
return res;
- }
+ };
/**
* Update the record
* @throws {ValidationError} If no valid fields are provided or ID is missing
* @throws {NotFoundError} If record is not found
*/
- async update(params, identifier = 'id') {
- if (!this[identifier]) throw new ValidationError('Record ID is required for update');
- const { updates, values, position } = this.create_update_fields(params);
- values.push(this[identifier]);
- const query_str = `UPDATE ${this.table} SET ${updates} WHERE ${identifier}=$${position} RETURNING *;`;
- logger.debug(`Update query: ${query_str}`);
- const result = await phsdb.query(query_str, values, { plain: true });
- if (!result) throw new NotFoundError(`Record not found in ${this.table}`);
- return this.instance(result);
- }
+ async update( params, identifier = 'id' ) {
+ if (!this[identifier]) throw new ValidationError( 'Record ID is required for update' );
+ const { updates, values, position } = this.create_update_fields( params );
+ values.push( this[identifier] );
+ const query_str = `UPDATE ${ this.table }
+ SET ${ updates }
+ WHERE ${ identifier } = $${ position } RETURNING *;`;
+ logger.debug( `Update query: ${ query_str }` );
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new NotFoundError( `Record not found in ${ this.table }` );
+ return this.instance( result );
+ };
/**
* Create update fields string and values
* @returns {{updates: string, values: any[], position: number}} Update query components
* @throws {ValidationError} If no valid fields are provided
*/
- create_update_fields(params) {
+ create_update_fields( params ) {
let position = 1;
const values = [];
let updates = '';
for (const column of this.default_columns) {
- if (params.hasOwnProperty(column) && !this.update_exclude_columns.includes(column)) {
+ if (params.hasOwnProperty( column ) && !this.update_exclude_columns.includes( column )) {
const value = params[column];
this[column] = value;
- values.push(value);
- updates += (updates ? ',' : '') + `${column}=$${position++}`;
+ values.push( value );
+ updates += (updates ? ',' : '') + `${ column }=$${ position++ }`;
}
}
- if (!updates) throw new ValidationError('No valid fields provided for update');
+ if (!updates) throw new ValidationError( 'No valid fields provided for update' );
return { updates, values, position };
- }
+ };
/**
* Serialize to JSON, excluding internal properties
* @returns {Object<any>} Serialized object
*/
toJSON() {
- const { default_columns, prepend, base_query, base_list_query, table, update_exclude_columns, instance, ...rest } = this;
+ const {
+ default_columns,
+ prepend,
+ base_query,
+ base_list_query,
+ table,
+ update_exclude_columns,
+ instance,
+ ...rest
+ } = this;
return rest;
- }
+ };
- // noinspection JSUnusedGlobalSymbols
/**
* Execute transaction
* @param {Function} callback - Transaction callback
* @returns {Promise<any>} Transaction result
* @throws {Error} If transaction fails
*/
- async with_transaction(callback) {
- const client = await require('../phsdb').get_client();
+ async with_transaction( callback ) {
+ const client = await require( '../phsdb' ).get_client();
try {
- await client.query('BEGIN');
- const result = await callback(client);
- await client.query('COMMIT');
+ await client.query( 'BEGIN' );
+ const result = await callback( client );
+ await client.query( 'COMMIT' );
return result;
} catch (error) {
- await client.query('ROLLBACK');
+ await client.query( 'ROLLBACK' );
throw error;
} finally {
- await require('../phsdb').client_release(client);
+ await require( '../phsdb' ).client_release( client );
}
- }
+ };
}
module.exports = { Model, NotFoundError, ValidationError };
\ No newline at end of file