logger.debug(`JWT-JWTPayload: ${JSON.stringify(JWTPayload)}`);
/*return new User().findOne({ email: JWTPayload.email, is_active: true })*/
// return new User().findOne({ id: 1, is_active: true })
- return phsdb.query(`select * from phs.users where id = $1;`, [1], { plain: true })
+ return phsdb.query(`select * from phase.users where id = $1;`, [1], { plain: true })
.then(async user => {
logger.debug('passport: ' + user);
if (!user?.id) {
--- /dev/null
+/**
+ * @file Address model for phase.addresses table
+ */
+
+const { Model, ValidationError } = require('./model');
+
+/**
+ * @typedef {Object} Address
+ * @property {number} id - Address ID (primary key)
+ * @property {string} street_1 - Street address line 1
+ * @property {string|null} street_2 - Street address line 2
+ * @property {string} city - City
+ * @property {string} state - State
+ * @property {string} zip_code - Zip code
+ * @property {number|null} created_by_id - ID of user who created this address
+ * @property {Date} created_at - Creation timestamp
+ * @property {boolean} is_deleted - Soft delete flag
+ * @property {number|null} deleted_by_id - ID of user who deleted this address
+ * @property {Date|null} deleted_at - Deletion timestamp
+ */
+
+/**
+ * Address model class
+ * @extends Model
+ */
+class Address extends Model {
+ /**
+ * Create an Address instance
+ * @param {Partial<Address>} [props] - Address properties
+ */
+ constructor(props) {
+ super(props);
+ this.table = 'phase.addresses';
+ this.prepend = 'a';
+ this.default_columns = [
+ 'id', 'street_1', 'street_2', 'city', 'state', 'zip_code',
+ 'created_by_id', 'created_at', 'is_deleted', 'deleted_by_id', 'deleted_at'
+ ];
+ this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id'];
+ this.base_query = `
+ SELECT a.id, a.street_1, a.street_2, a.city, a.state, a.zip_code,
+ a.created_by_id, a.created_at, a.is_deleted, a.deleted_by_id, a.deleted_at
+ FROM phase.addresses a
+ WHERE a.is_deleted = false
+ `;
+ this.base_list_query = `
+ SELECT a.id, a.street_1, a.street_2, a.city, a.state, a.zip_code,
+ a.created_by_id, a.created_at
+ FROM phase.addresses a
+ WHERE a.is_deleted = false
+ `;
+ this.default_order_by = 'ORDER BY a.zip_code ASC';
+ this.instance = _props => new Address(_props);
+ }
+
+ /**
+ * Create a new address
+ * @param {Omit<Address, 'id'|'created_at'|'is_deleted'|'deleted_by_id'|'deleted_at'>} address_data - Address data
+ * @returns {Promise<Address>} Created address instance
+ * @throws {ValidationError} If required fields are missing
+ */
+ static async create(address_data) {
+ const { street_1, street_2 = null, city, state, zip_code, created_by_id = null } = address_data;
+ if (!street_1 || !city || !state || !zip_code) {
+ throw new ValidationError('Missing required fields: street_1, city, state, zip_code');
+ }
+ const query_str = `
+ INSERT INTO phase.addresses (street_1, street_2, city, state, zip_code, created_by_id)
+ VALUES ($1, $2, $3, $4, $5, $6) RETURNING *;
+ `;
+ const values = [street_1, street_2, city, state, zip_code, created_by_id];
+ const result = await phsdb.query(query_str, values, { plain: true });
+ if (!result) throw new ValidationError('Failed to create address');
+ return new Address(result);
+ }
+
+ /**
+ * Create the relation between a user and an address
+ * @param user_id
+ * @returns {Promise<{user_id: number, address_id: number}>}
+ */
+ static async add_user(user_id) {
+ const done = await require( 'user_address.model' ).add_relation( user_id, this.id );
+ if (!done) throw new ValidationError('Failed to add phone number');
+ return done;
+ };
+
+ /**
+ * Find address by zip code
+ * @param {string} zip_code - Zip code to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<Address|null>[]} Address instance or null
+ */
+ static async find_by_zip_code(zip_code, excludes = []) {
+ return await new Address().find_many( { zip_code }, excludes );
+ }
+
+ /**
+ * Soft delete address
+ * @param {number|string} deleted_by_id - ID of user performing deletion
+ * @returns {Promise<Address>} Updated address instance
+ * @throws {ValidationError} If deleted_by_id is invalid
+ */
+ async soft_delete(deleted_by_id) {
+ const deleted_by_id_int = parseInt(deleted_by_id, 10);
+ if (isNaN(deleted_by_id_int)) {
+ throw new ValidationError('deleted_by_id must be a valid integer');
+ }
+ return await this.update({
+ is_deleted: true,
+ deleted_at: new Date().toISOString(),
+ deleted_by_id: deleted_by_id_int
+ });
+ }
+
+ /**
+ * Get the user who created this address
+ * @returns {Promise<User|null>} Creator user instance or null
+ */
+ async get_created_by() {
+ if (!this.created_by_id) return null;
+ const User = require('./user.model');
+ return await new User().find_one({ id: this.created_by_id }, []);
+ }
+}
+
+module.exports = Address;
\ No newline at end of file
-// noinspection JSUnusedGlobalSymbols
+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) {
+ super( 404, message );
+ this.name = 'NotFoundError';
+ }
+}
+
+/**
+ * Custom error for validation failures
+ * @extends HttpError
+ */
+class ValidationError extends HttpError {
+ /**
+ * @param {string} message - Error message
+ */
+ constructor(message) {
+ super(400, message);
+ this.name = 'ValidationError';
+ }
+}
+
+// noinspection DuplicatedCode
+/**
+ * Base model class for database operations
+ */
class Model {
- constructor( props ) {
- props && Object.keys( props ).forEach( c => {
- this[c] = props[c];
- } );
+ /**
+ * Create a model instance
+ * @param {Object} [props] - Properties to initialize the model
+ */
+ constructor(props) {
+ props && Object.keys(props).forEach(c => { this[c] = props[c]; });
+ /** @type {string} Database table name */
this.table = '';
- this.defaultColumns = [];
- this.updateExcludeColumns = ['id'];
+ /** @type {string[]} Allowed columns for queries */
+ this.default_columns = [];
+ /** @type {string[]} Columns excluded from updates */
+ this.update_exclude_columns = ['id'];
+ /** @type {string} Table alias for queries */
this.prepend = '';
- this.baseQuery = '';
- this.baseListQuery = '';
- this.defaultOrderBy = undefined;
- this.instance = _props => new Model( _props );
- };
+ /** @type {string} Base query for single record retrieval */
+ this.base_query = '';
+ /** @type {string} Base query for multiple record retrieval */
+ this.base_list_query = '';
+ /** @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);
+ /** @type {string|undefined} GROUP BY clause */
+ this.group_by = undefined;
+ }
- whereClause = ( keys, prepend ) => keys?.length > 0 ? `where ${ keys.map( ( k, index ) => `${ prepend }.${ k } = $${ index + 1 }` ).join( ' and ' ) }` : '';
+ /**
+ * Build WHERE clause for query
+ * @param {string[]} keys - Column names
+ * @param {string} prepend - Table alias
+ * @returns {string} WHERE clause
+ */
+ where_clause(keys, prepend) {
+ if (!keys?.length) return '';
+ return `WHERE ${keys.map((k, index) => `${prepend}.${k} = $${index + 1}`).join(' AND ')}`;
+ }
/**
- * Build where clause for query
- *
- * @param {*} where
- * @param {*} defaultColumns
- * @returns
+ * Filter WHERE object to valid columns
+ * @param {Object} where - Conditions for the WHERE clause
+ * @param {string[]} default_columns - Allowed columns
+ * @returns {{keys: string[], values: any[]}} Keys and values for the query
*/
- buildWhere = function ( where, defaultColumns ) {
+ build_where(where, default_columns) {
const keys = [];
const values = [];
- where && Object.keys( where ).forEach( k => {
- if (defaultColumns.includes( k )) {
- keys.push( k );
- values.push( where[k] );
- }
- } );
+ if (where) {
+ Object.keys(where).forEach(k => {
+ if (default_columns.includes(k)) {
+ keys.push(k);
+ values.push(where[k]);
+ }
+ });
+ }
return { keys, values };
- };
+ }
/**
* Find one record
- *
- * @param {*} where
- * @param {*} [excludes]
- * @returns
+ * @param {Object} where - Conditions for the WHERE clause
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<Object|null>} Found record or null
*/
- findOne = function ( where, excludes = [] ) {
- const self = this;
- const { keys, values } = self.buildWhere( where, self.defaultColumns );
- return phsdb.query( `
- ${ self.baseQuery }
- ${ self.whereClause( keys, self.prepend ) }
- ${ self.groupBy ? self.groupBy : '' }
- `, values, { plain:true } ).then( result => {
- const found = self.instance( result );
- excludes?.map( e => delete found[e] );
- return found;
- } );
- };
+ 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 : ''}`,
+ values,
+ { plain: true }
+ );
+ if (!result) return null;
+ const found = this.instance(result);
+ excludes?.forEach(e => delete found[e]);
+ return found;
+ }
+ // noinspection JSUnusedGlobalSymbols
/**
- * Find one record
- *
- * @param {*} where
- * @param {*} [excludes]
- * @returns
+ * 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
*/
- findOneSimple = function ( where, excludes = [] ) {
- const self = this;
- const { keys, values } = self.buildWhere( where, self.defaultColumns );
- return phsdb.query( `
- ${ self.baseQuery }
- ${ self.whereClause( keys, self.prepend ) }
- ${ self.groupBy ? self.groupBy : '' }
- `, values, { plain:true } ).then( result => {
- excludes?.map( e => delete result[e] );
- return result?.length > 0 ? result : null;
- } );
- };
+ 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 : ''}`,
+ values,
+ { plain: true }
+ );
+ if (!result) return null;
+ excludes?.forEach(e => delete result[e]);
+ return result;
+ }
/**
- * @typedef {Object} orderBy
- * @property {string} name
- * @property {('asc', 'desc')} direction
+ * Get base list query
+ * @returns {string} Base list query
*/
-
- getBaseListQuery = function () {
- const self = this;
- return `
- ${ self.baseListQuery }
- `;
- };
+ get_base_list_query() {
+ return `${this.base_list_query}`;
+ }
/**
* Find many records
- *
- * @param {Object|undefined} [where]
- * @param {string[]|undefined} [excludes]
- * @param {orderBy|undefined} [orderBy] This can needs to be passed as an object with a name and optionally a direction. You can look at the vehicle controller and model for a good example.
- * @returns
+ * @param {Object} [where] - Conditions for the WHERE clause
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit=100] - Maximum number of records
+ * @param {number} [offset=0] - Number of records to skip
+ * @returns {Promise<Object[]>} Array of records
*/
- findMany = function ( where, excludes, orderBy ) {
- const self = this;
- const { keys, values } = self.buildWhere( where, self.defaultColumns );
- return phsdb.query( `
- ${ self.getBaseListQuery( self.whereClause( keys, self.prepend ) ) }
- ${ self.whereClause( keys, self.prepend ) }
- ${ self.groupBy ? self.groupBy : '' }
- ${ orderBy ? `order by ${ orderBy.name } ${ orderBy.direction ?? 'asc' }` : self.defaultOrderBy ?? '' };
- `, values ).then( async results => {
- const res = [];
- await results.forEach( result => {
- const found = self.instance( result );
- excludes?.map( e => delete found[e] );
- return res.push( found );
- } );
- return res;
- } );
- };
+ 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}
+ `,
+ values
+ );
+ const res = [];
+ for (const result of results) {
+ const found = this.instance(result);
+ excludes?.forEach(e => delete found[e]);
+ res.push(found);
+ }
+ return res;
+ }
+ // noinspection JSUnusedGlobalSymbols
/**
- * Find many records
- *
- * @param {Object|undefined} [where]
- * @param {string[]|undefined} [excludes]
- * @param {orderBy|undefined} [orderBy] This can needs to be passed as an object with a name and optionally a direction. You can look at the vehicle controller and model for a good example.
- * @returns
+ * Find many records (raw data)
+ * @param {Object} [where] - Conditions for the WHERE clause
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit=100] - Maximum number of records
+ * @param {number} [offset=0] - Number of records to skip
+ * @returns {Promise<Object[]>} Array of records
*/
- findManyLite = function ( where, excludes, orderBy ) {
- const self = this;
- const { keys, values } = self.buildWhere( where, self.defaultColumns );
- return phsdb.query( `
- ${ self.getBaseListQuery( self.whereClause( keys, self.prepend ) ) }
- ${ self.whereClause( keys, self.prepend ) }
- ${ self.groupBy ? self.groupBy : '' }
- ${ orderBy ? `order by ${ orderBy.name } ${ orderBy.direction ?? 'asc' }` : self.defaultOrderBy ?? '' };
- `, values ).then( async results => {
- const res = [];
- await results.forEach( result => {
- const dataValues = {};
- Object.keys( result ).map( k => dataValues[k] = result[k] );
- excludes?.map( e => delete dataValues[e] );
- return res.push( dataValues );
- } );
- return res;
- } );
- };
+ 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}
+ `,
+ 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);
+ }
+ return res;
+ }
/**
- * Update the record. Will update only the fields present
- * in the params
- *
- * @param {*} params
- * @returns
+ * Update the record
+ * @param {Object} params - Fields to update
+ * @param {string} [identifier='id'] - Identifier field for update
+ * @returns {Promise<Object>} Updated record
+ * @throws {ValidationError} If no valid fields are provided or ID is missing
+ * @throws {NotFoundError} If record is not found
*/
- update = async function ( params ) {
- const self = this;
-
- // build list of update values
- const { updates, values, position } = self.createUpdateFields( params );
-
- // add id for where query
- values.push( this.id );
-
- // build query from values
- const query = 'update ' + this.table + ' set ' + updates + ' where id=$' + position + ' returning *;';
- logger.debug( `query: ${ query }` );
-
- return await phsdb.query( query, values, { plain:true } )
- .then( () => {
- return self.findOne( { id:this.id } );
- } );
- };
-
- toJSON = function () {
- const {
- defaultColumns,
- prepend,
- baseQuery,
- baseListQuery,
- table,
- updateExcludeColumns,
- agingDownloadQuery,
- baseListExtendedQuery,
- baseListQueryForTech,
- instance,
- db,
- defaultOrderBy,
- excludes,
- ...rest
- } = this;
- return rest;
- };
+ 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 based on
- * the params and the column configuration
+ * Create update fields string and values
+ * @param {Object} params - Update parameters
+ * @returns {{updates: string, values: any[], position: number}} Update query components
+ * @throws {ValidationError} If no valid fields are provided
*/
- createUpdateFields( params ) {
+ create_update_fields(params) {
let position = 1;
- let values = [];
+ const values = [];
let updates = '';
- for (const column of this.defaultColumns) {
- if (params.hasOwnProperty( column ) && !this.updateExcludeColumns.includes( column )) {
+ for (const column of this.default_columns) {
+ 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');
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;
+ 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();
+ try {
+ await client.query('BEGIN');
+ const result = await callback(client);
+ await client.query('COMMIT');
+ return result;
+ } catch (error) {
+ await client.query('ROLLBACK');
+ throw error;
+ } finally {
+ await require('../phsdb').client_release(client);
+ }
+ }
}
-module.exports = Model;
\ No newline at end of file
+module.exports = { Model, NotFoundError, ValidationError };
\ No newline at end of file
--- /dev/null
+/**
+ * @file Nickname model for phase.nicknames table
+ */
+
+const { Model, ValidationError } = require('./model');
+const User = require( './user.model' );
+
+/**
+ * @typedef {Object} Nickname
+ * @property {number} id - Nickname ID (primary key)
+ * @property {string} nickname - Nickname value
+ * @property {number|null} created_by_id - ID of user who created this nickname
+ * @property {Date} created_at - Creation timestamp
+ * @property {boolean} is_deleted - Soft delete flag
+ * @property {number|null} deleted_by_id - ID of user who deleted this nickname
+ * @property {Date|null} deleted_at - Deletion timestamp
+ */
+
+/**
+ * Nickname model class
+ * @extends Model
+ */
+class Nickname extends Model {
+ /**
+ * Create a Nickname instance
+ * @param {Partial<Nickname>} [props] - Nickname properties
+ */
+ constructor(props) {
+ super(props);
+ this.table = 'phase.nicknames';
+ this.prepend = 'n';
+ this.default_columns = [
+ 'id', 'nickname', 'created_by_id', 'created_at',
+ 'is_deleted', 'deleted_by_id', 'deleted_at'
+ ];
+ this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id'];
+ this.base_query = `
+ SELECT n.id, n.nickname, n.created_by_id, n.created_at,
+ n.is_deleted, n.deleted_by_id, n.deleted_at
+ FROM phase.nicknames n
+ WHERE n.is_deleted = false
+ `;
+ this.base_list_query = `
+ SELECT n.id, n.nickname, n.created_by_id, n.created_at
+ FROM phase.nicknames n
+ WHERE n.is_deleted = false
+ `;
+ this.default_order_by = 'ORDER BY n.nickname ASC';
+ this.instance = _props => new Nickname(_props);
+ }
+
+ /**
+ * Create a new nickname
+ * @param {Omit<Nickname, 'id'|'created_at'|'is_deleted'|'deleted_by_id'|'deleted_at'>} nickname_data - Nickname data
+ * @returns {Promise<Nickname>} Created nickname instance
+ * @throws {ValidationError} If required fields are missing
+ */
+ static async create(nickname_data) {
+ const { nickname, created_by_id = null } = nickname_data;
+ if (!nickname) throw new ValidationError('Missing required field: nickname');
+ const query_str = `
+ INSERT INTO phase.nicknames (nickname, created_by_id)
+ VALUES ($1, $2) RETURNING *;
+ `;
+ const values = [nickname, created_by_id];
+ const result = await phsdb.query(query_str, values, { plain: true });
+ if (!result) throw new ValidationError('Failed to create nickname');
+ return new Nickname(result);
+ };
+
+ /**
+ * Create the relation between a user and a nickname
+ * @param user_id
+ * @returns {Promise<{user_id: number, nickname_id: number}>}
+ */
+ static async add_user(user_id) {
+ const done = await require( 'user_nickname.model' ).add_relation( user_id, this.id );
+ if (!done) throw new ValidationError('Failed to add nickname');
+ return done;
+ };
+
+ async get_with_associated_users( id = null, excludes = [] ) {
+ const id_int = parseInt( id, 10 );
+ let query_str = `
+ SELECT n.id, n.nickname, n.created_by_id, n.created_at, concat(db.first_name, ' ', db.last_name) as deleted_by,
+ n.is_deleted, n.deleted_by_id, n.deleted_at, concat(cr.first_name, ' ', cr.last_name) as created_by
+ FROM phase.nicknames n
+ LEFT JOIN phase.users cr ON n.created_by_id = cr.id
+ LEFT JOIN phase.users db ON n.deleted_by_id = db.id
+ WHERE n.is_deleted = false
+ `;
+ const values = [];
+ if (id_int) {
+ query_str += ` AND n.id = $1;`;
+ values.push( id_int );
+ }
+ const result = await phsdb.query(query_str, values, { plain: !isNaN(id_int) } );
+ if (!result) throw new ValidationError('Failed to get nickname');
+ else return result;
+ };
+
+ /**
+ * Find nickname by value
+ * @param {string} nickname - Nickname to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<Nickname|null>} Nickname instance or null
+ */
+ static async find_by_nickname(nickname, excludes = []) {
+ return await new Nickname().find_one({ nickname }, excludes);
+ }
+
+ /**
+ * Soft delete a nickname and remove the relation between the nickname and any users that it is associated with.
+ * @param {number|string} deleted_by_id - ID of user performing deletion
+ * @returns {Promise<Nickname>} Updated nickname instance
+ * @throws {ValidationError} If deleted_by_id is invalid
+ */
+ async soft_delete(deleted_by_id) {
+ const deleted_by_id_int = parseInt(deleted_by_id, 10);
+ if (isNaN(deleted_by_id_int)) {
+ throw new ValidationError('deleted_by_id must be a valid integer');
+ }
+ await require( 'user_nickname.model' ).remove_relation( this.id );
+ return await this.update({
+ is_deleted: true,
+ deleted_at: new Date().toISOString(),
+ deleted_by_id: deleted_by_id_int
+ });
+ }
+
+ /**
+ * Get the user who created this nickname
+ * @returns {Promise<User|null>} Creator user instance or null
+ */
+ async get_created_by() {
+ if (!this.created_by_id) return null;
+ const User = require('./user.model');
+ return await new User().find_one({ id: this.created_by_id }, []);
+ }
+
+ /**
+ * Get the user who deleted this nickname
+ * @returns {Promise<User|null>} Deleter user instance or null
+ */
+ async get_deleted_by() {
+ if (!this.deleted_by_id) return null;
+ const User = require('./user.model');
+ return await new User().find_one({ id: this.deleted_by_id }, []);
+ }
+}
+
+module.exports = Nickname;
\ No newline at end of file
--- /dev/null
+/**
+ * @file Phone number model for phase.phone_numbers table
+ */
+
+const { Model, ValidationError } = require('./model');
+
+/**
+ * @typedef {Object} PhoneNumber
+ * @property {number} id - Phone number ID (primary key)
+ * @property {string} name - Name of the phone number (e.g., 'mobile')
+ * @property {string} type - Type of phone number (e.g., 'mobile')
+ * @property {string} number - Phone number
+ * @property {number|null} created_by_id - ID of user who created this phone number
+ * @property {Date} created_at - Creation timestamp
+ * @property {boolean} is_deleted - Soft delete flag
+ * @property {number|null} deleted_by_id - ID of user who deleted this phone number
+ * @property {Date|null} deleted_at - Deletion timestamp
+ */
+
+/**
+ * Phone number model class
+ * @extends Model
+ */
+class PhoneNumber extends Model {
+ /**
+ * Create a PhoneNumber instance
+ * @param {Partial<PhoneNumber>} [props] - Phone number properties
+ */
+ constructor(props) {
+ super(props);
+ this.table = 'phase.phone_numbers';
+ this.prepend = 'pn';
+ this.default_columns = [
+ 'id', 'name', 'type', 'number', 'created_by_id', 'created_at',
+ 'is_deleted', 'deleted_by_id', 'deleted_at'
+ ];
+ this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id'];
+ this.base_query = `
+ SELECT pn.id, pn.name, pn.type, pn.number, pn.created_by_id, pn.created_at,
+ pn.is_deleted, pn.deleted_by_id, pn.deleted_at
+ FROM phase.phone_numbers pn
+ WHERE pn.is_deleted = false
+ `;
+ this.base_list_query = `
+ SELECT pn.id, pn.name, pn.type, pn.number, pn.created_by_id, pn.created_at
+ FROM phase.phone_numbers pn
+ WHERE pn.is_deleted = false
+ `;
+ this.default_order_by = 'ORDER BY pn.number ASC';
+ this.instance = _props => new PhoneNumber(_props);
+ }
+
+ /**
+ * Create a new phone number
+ * @param {Omit<PhoneNumber, 'id'|'created_at'|'is_deleted'|'deleted_by_id'|'deleted_at'>} phone_data - Phone number data
+ * @returns {Promise<PhoneNumber>} Created phone number instance
+ * @throws {ValidationError} If required fields are missing
+ */
+ static async create(phone_data) {
+ const { name = 'mobile', type = 'mobile', number, created_by_id = null } = phone_data;
+ if (!number) throw new ValidationError('Missing required field: number');
+ const query_str = `
+ INSERT INTO phase.phone_numbers (name, type, number, created_by_id)
+ VALUES ($1, $2, $3, $4) RETURNING *;
+ `;
+ const values = [name, type, number, created_by_id];
+ const result = await phsdb.query(query_str, values, { plain: true });
+ if (!result) throw new ValidationError('Failed to create phone number');
+ return new PhoneNumber(result);
+ };
+
+ static async add_user(user_id) {
+ const done = await require( 'user_phone_number.model' ).add_relation( user_id, this.id );
+ if (!done) throw new ValidationError('Failed to add phone number');
+ return done;
+ };
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Find phone number by number
+ * @param {string} number - Phone number to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<PhoneNumber|null>} Phone number instance or null
+ */
+ static async find_by_number(number, excludes = []) {
+ return await new PhoneNumber().find_one({ number }, excludes);
+ }
+
+ /**
+ * Soft delete phone number
+ * @param {number|string} deleted_by_id - ID of user performing deletion
+ * @returns {Promise<PhoneNumber>} Updated phone number instance
+ * @throws {ValidationError} If deleted_by_id is invalid
+ */
+ async soft_delete(deleted_by_id) {
+ const deleted_by_id_int = parseInt(deleted_by_id, 10);
+ if (isNaN(deleted_by_id_int)) {
+ throw new ValidationError('deleted_by_id must be a valid integer');
+ }
+ return await this.update({
+ is_deleted: true,
+ deleted_at: new Date().toISOString(),
+ deleted_by_id: deleted_by_id_int
+ });
+ }
+
+ /**
+ * Get the user who created this phone number
+ * @returns {Promise<User|null>} Creator user instance or null
+ */
+ async get_created_by() {
+ if (!this.created_by_id) return null;
+ const User = require('./user.model');
+ return await new User().find_one({ id: this.created_by_id }, []);
+ }
+}
+
+module.exports = PhoneNumber;
\ No newline at end of file
--- /dev/null
+/**
+ * @file User model for the users table
+ */
+
+const { Model, ValidationError } = require('./model');
+
+/**
+ * @typedef {Object} User
+ * @property {number} id - User ID (primary key)
+ * @property {string} email - User's email (unique)
+ * @property {string} first_name - User's first name
+ * @property {string} middle_name - User's middle name
+ * @property {string} last_name - User's last name
+ * @property {string|null} initials - User's initials
+ * @property {number|null} created_by_id - ID of user who created this user
+ * @property {Date} created_at - Creation timestamp
+ * @property {boolean} is_deleted - Soft delete flag
+ * @property {number|null} deleted_by_id - ID of user who deleted this user
+ * @property {Date|null} deleted_at - Deletion timestamp
+ * @property {boolean} is_active - Whether user is active
+ * @property {number|null} deactivated_by_id - ID of user who deactivated this user
+ * @property {Date|null} deactivated_at - Deactivation timestamp
+ */
+
+/**
+ * User model class
+ * @extends Model
+ */
+class User extends Model {
+ /**
+ * Create a User instance
+ * @param {Partial<User>} [props] - User properties
+ */
+ constructor(props) {
+ super(props);
+ this.table = 'phase.users';
+ this.prepend = 'u';
+ this.default_columns = [
+ 'id', 'email', 'first_name', 'middle_name', 'last_name', 'initials',
+ 'created_by_id', 'created_at', 'is_deleted', 'deleted_by_id', 'deleted_at',
+ 'is_active', 'deactivated_by_id', 'deactivated_at'
+ ];
+ this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id'];
+ this.base_query = `
+ SELECT u.id, u.email, u.first_name, u.middle_name, u.last_name, u.initials,
+ u.created_by_id, u.created_at, u.is_deleted, u.deleted_by_id, u.deleted_at,
+ u.is_active, u.deactivated_by_id, u.deactivated_at
+ FROM phase.users u
+ WHERE u.is_deleted = false
+ `;
+ this.base_list_query = `
+ SELECT u.id, u.email, u.first_name, u.middle_name, u.last_name, u.initials,
+ u.created_by_id, u.created_at, u.is_active, u.deactivated_by_id, u.deactivated_at
+ FROM phase.users u
+ WHERE u.is_deleted = false
+ `;
+ this.default_order_by = 'ORDER BY u.email ASC';
+ this.instance = _props => new User(_props);
+ }
+
+ /**
+ * Create a new user
+ * @param {Omit<User, 'id'|'created_at'|'is_deleted'|'deleted_by_id'|'deleted_at'>} user_data - User data
+ * @returns {Promise<User>} Created user instance
+ * @throws {ValidationError} If required fields are missing
+ */
+ static async create(user_data) {
+ const {
+ email, first_name, middle_name = '', last_name, initials = null,
+ created_by_id = null, is_active = true, deactivated_by_id = null, deactivated_at = null
+ } = user_data;
+ if (!email || !first_name || !last_name) {
+ throw new ValidationError('Missing required fields: email, first_name, last_name');
+ }
+ const query_str = `
+ INSERT INTO phase.users (
+ email, first_name, middle_name, last_name, initials, created_by_id,
+ is_active, deactivated_by_id, deactivated_at
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;
+ `;
+ const values = [
+ email, first_name, middle_name, last_name, initials, created_by_id,
+ is_active, deactivated_by_id, deactivated_at
+ ];
+ const result = await phsdb.query(query_str, values, { plain: true });
+ if (!result) throw new ValidationError('Failed to create user');
+ await this.createPhoneNumbers();
+ await this.createAddresses();
+ await this.createNickNames();
+ return new User(result);
+ };
+
+ static async createPhoneNumbers( phone_numbers) {
+ if (phone_numbers?.length > 0) {
+ const pn = Array.from( new Set( phone_numbers ) );
+ const phoneArray = [];
+ for (let phone_number of pn) {
+ const phone = await require('./phone_number.model').create(phone_number);
+ await phone.add_user(this.id)
+ .then( result => phoneArray.push(result) )
+ .catch( err => throw new ValidationError( err.message ) );
+ }
+ } else return true;
+ };
+
+ static async createAddresses( addresses) {
+ if (addresses?.length > 0) {
+ const addr = Array.from( new Set( addresses ) );
+ const addrArray = [];
+ for (let address of addr) {
+ const _address = await require('./address.model').create(address);
+ await _address.add_user(this.id)
+ .then( result => addrArray.push(result) )
+ .catch( err => throw new ValidationError( err.message ) );
+ }
+ } else return true;
+ };
+
+ static async createNickNames( nicknames) {
+ if (nicknames?.length > 0) {
+ const names = Array.from( new Set( nicknames ) );
+ const nameArray = [];
+ for (let name of names) {
+ const nickname = await require('./nickname.model').create(name);
+ await nickname.add_relation(this.id)
+ .then( result => nameArray.push(result) )
+ .catch( err => throw new ValidationError( err.message ) );
+ }
+ } else return true;
+ };
+
+ /**
+ * Find user by email
+ * @param {string} email - Email to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<User|null>} User instance or null
+ */
+ static async find_by_email(email, excludes = []) {
+ return await new User().find_one({ email }, excludes);
+ }
+
+ /**
+ * Find active users
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit=100] - Maximum number of records
+ * @param {number} [offset=0] - Number of records to skip
+ * @returns {Promise<User[]>} Array of active users
+ */
+ static async find_active(excludes = [], order_by = null, limit = 100, offset = 0) {
+ return await new User().find_many({ is_active: true }, excludes, order_by, limit, offset);
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Find deleted users
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration
+ * @param {number} [limit=100] - Maximum number of records
+ * @param {number} [offset=0] - Number of records to skip
+ * @returns {Promise<User[]>} Array of deleted users
+ */
+ static async find_deleted(excludes = [], order_by = null, limit = 100, offset = 0) {
+ const query_str = this.prototype.base_list_query.replace('WHERE u.is_deleted = false', 'WHERE u.is_deleted = true');
+ const instance = this.prototype.instance;
+ const { keys, values } = this.prototype.build_where({}, this.prototype.default_columns);
+ values.push(limit, offset);
+ const results = await phsdb.query(
+ `
+ ${query_str}
+ ${this.prototype.where_clause(keys, this.prototype.prepend)}
+ ${order_by ? `ORDER BY ${order_by.name} ${order_by.direction ?? 'asc'}` : this.prototype.default_order_by ?? ''}
+ LIMIT $${values.length - 1} OFFSET $${values.length}
+ `,
+ values
+ );
+ return results.map(result => {
+ const found = instance(result);
+ excludes?.forEach(e => delete found[e]);
+ return found;
+ });
+ }
+
+ /**
+ * Deactivate user
+ * @param {number|string} deactivated_by_id - ID of user performing deactivation
+ * @returns {Promise<User>} Updated user instance
+ * @throws {ValidationError} If deactivated_by_id is invalid
+ */
+ async deactivate(deactivated_by_id) {
+ const deactivated_by_id_int = parseInt(deactivated_by_id, 10);
+ if (isNaN(deactivated_by_id_int)) {
+ throw new ValidationError('deactivated_by_id must be a valid integer');
+ }
+ return await this.update({
+ is_active: false,
+ deactivated_at: new Date().toISOString(),
+ deactivated_by_id: deactivated_by_id_int
+ });
+ }
+
+ /**
+ * Reactivate user
+ * @returns {Promise<User>} Updated user instance
+ */
+ async reactivate() {
+ return await this.update({
+ is_active: true,
+ deactivated_at: null,
+ deactivated_by_id: null
+ });
+ }
+
+ /**
+ * Soft delete user
+ * @param {number|string} deleted_by_id - ID of user performing deletion
+ * @returns {Promise<User>} Updated user instance
+ * @throws {ValidationError} If deleted_by_id is invalid
+ */
+ async soft_delete(deleted_by_id) {
+ const deleted_by_id_int = parseInt(deleted_by_id, 10);
+ if (isNaN(deleted_by_id_int)) {
+ throw new ValidationError('deleted_by_id must be a valid integer');
+ }
+ return await this.update({
+ is_deleted: true,
+ deleted_at: new Date().toISOString(),
+ deleted_by_id: deleted_by_id_int
+ });
+ }
+
+ /**
+ * Check if user is active
+ * @returns {boolean} True if user is active
+ */
+ is_active() {
+ return this.is_active === true;
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Get the user who created this user
+ * @returns {Promise<User|null>} Creator user instance or null
+ */
+ async get_created_by() {
+ if (!this.created_by_id) return null;
+ return await new User().find_one({ id: this.created_by_id }, []);
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Get user data without sensitive information
+ * @returns {Omit<User, 'password'>} User data
+ */
+ to_safe_json() {
+ const { password, ...safe_data } = this.toJSON();
+ // noinspection JSValidateTypes
+ return safe_data;
+ }
+}
+
+module.exports = User;
\ No newline at end of file
--- /dev/null
+/**
+ * @file User addresses model for phase.user_addresses table
+ */
+
+const { Model, ValidationError } = require( './model' );
+
+/**
+ * @typedef {Object} UserAddress
+ * @property {number} user_id - User ID (foreign key)
+ * @property {number} address_id - Address ID (foreign key)
+ */
+
+/**
+ * User address model class
+ * @extends Model
+ */
+class UserAddress extends Model {
+ /**
+ * Create a UserAddress instance
+ * @param {Partial<UserAddress>} [props] - User address properties
+ */
+ constructor( props ) {
+ super( props );
+ this.table = 'phase.user_addresses';
+ this.prepend = 'ua';
+ this.default_columns = ['user_id', 'address_id'];
+ this.update_exclude_columns = ['user_id', 'address_id'];
+ this.base_query = `
+ SELECT ua.user_id, ua.address_id
+ FROM phase.user_addresses ua
+ `;
+ this.base_list_query = this.base_query;
+ this.default_order_by = 'ORDER BY ua.user_id ASC';
+ this.instance = _props => new UserAddress( _props );
+ };
+
+ /**
+ * Add a user-address relation
+ * @param {number|string} user_id - User ID
+ * @param {number|string} address_id - Address ID
+ * @returns {Promise<UserAddress>} Created relation instance
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async add_relation( user_id, address_id ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const address_id_int = parseInt( address_id, 10 );
+ if (isNaN( user_id_int ) || isNaN( address_id_int )) {
+ throw new ValidationError( 'user_id and address_id must be valid integers' );
+ }
+ const query_str = `
+ INSERT INTO phase.user_addresses (user_id, address_id)
+ VALUES ($1, $2)
+ RETURNING *;
+ `;
+ const values = [user_id_int, address_id_int];
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new ValidationError( 'Failed to add user-address relation' );
+ return new UserAddress( result );
+ };
+
+ /**
+ * Remove a user-address relation
+ * @param {number|string} address_id - Address ID
+ * @param {number|string|undefined} [user_id = undefined] - User ID
+ * @returns {Promise<UserAddress|null>} Deleted relation instance or null
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async remove_relation( address_id, user_id = undefined ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const address_id_int = parseInt( address_id, 10 );
+ if (isNaN( address_id_int )) {
+ throw new ValidationError( 'user_id and address_id must be valid integers' );
+ }
+ const query_str = `
+ DELETE
+ FROM phase.user_addresses
+ WHERE address_id = $1 ${ user_id_int ? 'AND user_id = $2' : '' }
+ RETURNING *;
+ `;
+ const values = [address_id_int];
+ if (!isNaN( user_id_int )) values.push( user_id_int );
+ const result = await phsdb.query( query_str, values, { plain:!isNaN( user_id_int ) } );
+ return result ? new UserAddress( result ) : null;
+ };
+}
+
+module.exports = UserAddress;
\ No newline at end of file
--- /dev/null
+/**
+ * @file User nicknames model for phase.user_nicknames table
+ */
+
+const { Model, ValidationError } = require( './model' );
+
+/**
+ * @typedef {Object} UserNickname
+ * @property {number} user_id - User ID (foreign key)
+ * @property {number} nickname_id - Nickname ID (foreign key)
+ */
+
+/**
+ * User nickname model class
+ * @extends Model
+ */
+class UserNickname extends Model {
+ /**
+ * Create a UserNickname instance
+ * @param {Partial<UserNickname>} [props] - User nickname properties
+ */
+ constructor( props ) {
+ super( props );
+ this.table = 'phase.user_nicknames';
+ this.prepend = 'un';
+ this.default_columns = ['user_id', 'nickname_id'];
+ this.update_exclude_columns = ['user_id', 'nickname_id'];
+ this.base_query = `
+ SELECT un.user_id, un.nickname_id
+ FROM phase.user_nicknames un
+ `;
+ this.base_list_query = this.base_query;
+ this.default_order_by = 'ORDER BY un.user_id ASC';
+ this.instance = _props => new UserNickname( _props );
+ };
+
+ /**
+ * Add a user-nickname relation
+ * @param {number|string} user_id - User ID
+ * @param {number|string} nickname_id - Nickname ID
+ * @returns {Promise<UserNickname>} Created relation instance
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async add_relation( user_id, nickname_id ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const nickname_id_int = parseInt( nickname_id, 10 );
+ if (isNaN( user_id_int ) || isNaN( nickname_id_int )) {
+ throw new ValidationError( 'user_id and nickname_id must be valid integers' );
+ }
+ const query_str = `
+ INSERT INTO phase.user_nicknames (user_id, nickname_id)
+ VALUES ($1, $2)
+ RETURNING *;
+ `;
+ const values = [user_id_int, nickname_id_int];
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new ValidationError( 'Failed to add user-nickname relation' );
+ return new UserNickname( result );
+ };
+
+ /**
+ * Remove a user-nickname relation
+ * @param {number|string} nickname_id - Nickname ID
+ * @param {number|string|undefined} [user_id = undefined] - User ID
+ * @returns {Promise<UserNickname|null>} Deleted relation instance or null
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async remove_relation( nickname_id, user_id = undefined ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const nickname_id_int = parseInt( nickname_id, 10 );
+ if (isNaN( nickname_id_int )) {
+ throw new ValidationError( 'nickname_id must be valid integers' );
+ }
+ const query_str = `
+ DELETE
+ FROM phase.user_nicknames
+ WHERE nickname_id = $1 ${ user_id ? 'AND user_id = $2' : '' }
+ RETURNING *;
+ `;
+ const values = [nickname_id_int];
+ if (!isNaN( user_id_int )) values.push( user_id_int );
+ const result = await phsdb.query( query_str, values, { plain: !isNaN( user_id_int ) } );
+ return result ? new UserNickname( result ) : null;
+ };
+}
+
+module.exports = UserNickname;
\ No newline at end of file
--- /dev/null
+/**
+ * @file User phone numbers model for phase.user_phone_numbers table
+ */
+
+const { Model, ValidationError } = require( './model' );
+
+/**
+ * @typedef {Object} UserPhoneNumber
+ * @property {number} user_id - User ID (foreign key)
+ * @property {number} phone_number_id - Phone number ID (foreign key)
+ */
+
+/**
+ * User phone number model class
+ * @extends Model
+ */
+class UserPhoneNumber extends Model {
+ /**
+ * Create a UserPhoneNumber instance
+ * @param {Partial<UserPhoneNumber>} [props] - User phone number properties
+ */
+ constructor( props ) {
+ super( props );
+ this.table = 'phase.user_phone_numbers';
+ this.prepend = 'upn';
+ this.default_columns = ['user_id', 'phone_number_id'];
+ this.update_exclude_columns = ['user_id', 'phone_number_id'];
+ this.base_query = `
+ SELECT upn.user_id, upn.phone_number_id
+ FROM phase.user_phone_numbers upn
+ `;
+ this.base_list_query = this.base_query;
+ this.default_order_by = 'ORDER BY upn.user_id ASC';
+ this.instance = _props => new UserPhoneNumber( _props );
+ };
+
+ /**
+ * Add a user-phone number relation
+ * @param {number|string} user_id - User ID
+ * @param {number|string} phone_number_id - Phone number ID
+ * @returns {Promise<UserPhoneNumber>} Created relation instance
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async add_relation( user_id, phone_number_id ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const phone_number_id_int = parseInt( phone_number_id, 10 );
+ if (isNaN( user_id_int ) || isNaN( phone_number_id_int )) {
+ throw new ValidationError( 'user_id and phone_number_id must be valid integers' );
+ }
+ const query_str = `
+ INSERT INTO phase.user_phone_numbers (user_id, phone_number_id)
+ VALUES ($1, $2)
+ RETURNING *;
+ `;
+ const values = [user_id_int, phone_number_id_int];
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new ValidationError( 'Failed to add user-phone number relation' );
+ return new UserPhoneNumber( result );
+ };
+
+ /**
+ * Remove a user-phone number relation
+ * @param {number|string} phone_number_id - Phone number ID
+ * @param {number|string|undefined} [user_id = undefined] - User ID
+ * @returns {Promise<UserPhoneNumber|null>} Deleted relation instance or null
+ * @throws {ValidationError} If IDs are invalid
+ */
+ static async remove_relation( phone_number_id, user_id = undefined ) {
+ const user_id_int = parseInt( user_id, 10 );
+ const phone_number_id_int = parseInt( phone_number_id, 10 );
+ if (isNaN( phone_number_id_int )) {
+ throw new ValidationError( 'phone_number_id must be valid integers' );
+ }
+ let query_str = `
+ DELETE
+ FROM phase.user_phone_numbers
+ WHERE phone_number_id = $1 ${ user_id_int ? 'AND user_id = $2' : '' }
+ RETURNING *;
+ `;
+ const values = [phone_number_id_int];
+ if (!isNaN( user_id_int )) values.push( user_id_int );
+ const result = await phsdb.query( query_str, values, { plain:!isNaN( user_id_int ) } );
+ return result ? new UserPhoneNumber( result ) : null;
+ };
+}
+
+module.exports = UserPhoneNumber;
\ No newline at end of file
router.use( '/git', require( './git.routes' )( passport ) );
router.use( '/docker', require( './docker.routes' )( passport ) );
router.use( '/vpn', require( './vpn.routes' )( passport ) );
+ router.use( '/users', require( './user.routes' )( passport ) );
+ router.use( '/phone_numbers', require( './phone_numbers.routes' )( passport ) );
+ router.use( '/user_phone_numbers', require( './user_phone_numbers.routes' )( passport ) );
+ router.use( '/nicknames', require( './nicknames.routes' )( passport ) );
+ router.use( '/user_nicknames', require( './user_nicknames.routes' )( passport ) );
+ router.use( '/addresses', require( './addresses.routes' )( passport ) );
+ router.use( '/user_addresses', require( './user_addresses.routes' )( passport ) );
+ router.use( '/authentication', require( './authentication.routes' )( passport ) );
return router;
};
--- /dev/null
+const express = require('express');
+const router = express.Router();
+const { validate_auth } = require('../middleware/routeHelpers');
+const users_controller = require('../controllers/users.controller');
+
+module.exports = (passport) => {
+ router.post('/create', validate_auth(passport), users_controller.create);
+ router.get('/email/:email', validate_auth(passport), users_controller.find_by_email);
+ router.get('/active', validate_auth(passport), users_controller.find_active);
+ router.get('/:id', validate_auth(passport), users_controller.find_one);
+ router.get('/', validate_auth(passport), users_controller.find_many);
+ router.put('/:id', validate_auth(passport), users_controller.update);
+ router.put('/:id/deactivate', validate_auth(passport), users_controller.deactivate);
+ router.put('/:id/reactivate', validate_auth(passport), users_controller.reactivate);
+ router.put('/:id/soft_delete', validate_auth(passport), users_controller.soft_delete);
+ return router;
+};
\ No newline at end of file