/**
- * @file User model for the users table
+ * @file User model for phase.users table
*/
-const { Model, ValidationError } = require('./model');
+const { Model, ValidationError } = require( './model' );
/**
* @typedef {Object} User
* @property {string} middle_name - User's middle name
* @property {string} last_name - User's last name
* @property {string|null} initials - User's initials
+ * @property {string|null} nickname - User's nickname (unique)
* @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
* Create a User instance
* @param {Partial<User>} [props] - User properties
*/
- constructor(props) {
- super(props);
+ constructor( props ) {
+ super( props );
this.table = 'phase.users';
this.prepend = 'u';
this.default_columns = [
- 'id', 'email', 'first_name', 'middle_name', 'last_name', 'initials',
+ 'id', 'email', 'first_name', 'middle_name', 'last_name', 'initials', 'nickname',
'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
+ SELECT u.id,
+ u.email,
+ u.first_name,
+ u.middle_name,
+ u.last_name,
+ u.initials,
+ u.nickname,
+ 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
+ SELECT u.id,
+ u.email,
+ u.first_name,
+ u.middle_name,
+ u.last_name,
+ u.initials,
+ u.nickname,
+ 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);
+ this.instance = _props => new User( _props );
}
/**
* @returns {Promise<User>} Created user instance
* @throws {ValidationError} If required fields are missing
*/
- static async create(user_data) {
+ static async create( user_data ) {
const {
- email, first_name, middle_name = '', last_name, initials = null,
+ email, first_name, middle_name = '', last_name, initials = null, nickname = 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');
+ 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 *;
+ INSERT INTO phase.users (email, first_name, middle_name, last_name, initials, nickname, created_by_id,
+ is_active, deactivated_by_id, deactivated_at)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
+ RETURNING *;
`;
const values = [
- email, first_name, middle_name, last_name, initials, created_by_id,
- is_active, deactivated_by_id, deactivated_at
+ email, first_name, middle_name, last_name, initials, nickname,
+ 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;
+ const result = await phsdb.query( query_str, values, { plain:true } );
+ if (!result) throw new ValidationError( 'Failed to create user' );
+ return new User( result );
};
/**
* @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);
+ static async find_by_email( email, excludes = [] ) {
+ return await new User().find_one( { email }, excludes );
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ /**
+ * Find user by nickname
+ * @param {string} nickname - Nickname to search for
+ * @param {string[]} [excludes] - Fields to exclude from result
+ * @returns {Promise<User|null>} User instance or null
+ */
+ static async find_by_nickname( nickname, excludes = [] ) {
+ return await new User().find_one( { nickname }, excludes );
}
/**
* @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);
- }
+ 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
/**
* @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');
+ 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 { 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}
+ ${ 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 results.map( result => {
+ const found = instance( result );
+ excludes?.forEach( e => delete found[e] );
return found;
- });
+ } );
}
/**
* @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');
+ 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
- });
+ return await this.update( {
+ is_active:false,
+ deactivated_at:new Date().toISOString(),
+ deactivated_by_id:deactivated_by_id_int
+ } );
}
/**
* @returns {Promise<User>} Updated user instance
*/
async reactivate() {
- return await this.update({
- is_active: true,
- deactivated_at: null,
- deactivated_by_id: null
- });
+ return await this.update( {
+ is_active:true,
+ deactivated_at:null,
+ deactivated_by_id:null
+ } );
}
/**
* @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');
+ 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
- });
+ return await this.update( {
+ is_deleted:true,
+ deleted_at:new Date().toISOString(),
+ deleted_by_id:deleted_by_id_int
+ } );
}
/**
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