|
|
|
/* eslint-disable camelcase */
|
|
|
|
/* eslint-disable no-param-reassign */
|
|
|
|
import { Model, DataTypes, Op } from 'sequelize';
|
|
|
|
import { values, pick, isEqual, isNil, omitBy } from 'lodash';
|
|
|
|
import { hash, compare } from 'bcryptjs';
|
|
|
|
import moment from 'moment-timezone';
|
|
|
|
import httpStatus from 'http-status';
|
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
import APIError from '../utils/APIException';
|
|
|
|
import postgres from '../../config/postgres';
|
|
|
|
import { serviceName } from '../../config/vars';
|
|
|
|
import Permissions from '../../common/utils/Permissions';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create connection
|
|
|
|
*/
|
|
|
|
class User extends Model { }
|
|
|
|
const { sequelize } = postgres;
|
|
|
|
|
|
|
|
User.Providers = {
|
|
|
|
APPLE: 'apple',
|
|
|
|
GOOGLE: 'google',
|
|
|
|
FACEBOOK: 'facebook',
|
|
|
|
};
|
|
|
|
|
|
|
|
User.Statuses = {
|
|
|
|
INACTIVE: 'inactive',
|
|
|
|
ACTIVE: 'active',
|
|
|
|
BANNED: 'banned'
|
|
|
|
};
|
|
|
|
|
|
|
|
User.NameStatuses = {
|
|
|
|
INACTIVE: 'inactive',
|
|
|
|
ACTIVE: 'active',
|
|
|
|
BANNED: 'banned'
|
|
|
|
};
|
|
|
|
|
|
|
|
User.Genders = {
|
|
|
|
MALE: 'male',
|
|
|
|
FEMALE: 'female'
|
|
|
|
};
|
|
|
|
|
|
|
|
User.Services = {
|
|
|
|
USER: 'user',
|
|
|
|
STAFF: 'staff',
|
|
|
|
SERVICE: 'service'
|
|
|
|
};
|
|
|
|
|
|
|
|
User.Types = {
|
|
|
|
STAFF: 'staff',
|
|
|
|
COMPANY: 'company',
|
|
|
|
SUPPLIER: 'supplier',
|
|
|
|
INDIVIDUAL: 'individual'
|
|
|
|
};
|
|
|
|
|
|
|
|
const PUBLIC_FIELDS = [
|
|
|
|
'type',
|
|
|
|
'role',
|
|
|
|
'name',
|
|
|
|
'note',
|
|
|
|
'phone',
|
|
|
|
'email',
|
|
|
|
'avatar',
|
|
|
|
'cover',
|
|
|
|
'group',
|
|
|
|
'stores',
|
|
|
|
'gender',
|
|
|
|
'birthday',
|
|
|
|
'barcode',
|
|
|
|
'tax_code',
|
|
|
|
'company',
|
|
|
|
'country',
|
|
|
|
'address',
|
|
|
|
'province',
|
|
|
|
'district',
|
|
|
|
'ward',
|
|
|
|
'password',
|
|
|
|
'permissions'
|
|
|
|
];
|
|
|
|
|
|
|
|
const PUBLIC_PROFILE_FIELDS = [
|
|
|
|
'name',
|
|
|
|
'note',
|
|
|
|
'phone',
|
|
|
|
'email',
|
|
|
|
'avatar',
|
|
|
|
'cover',
|
|
|
|
'gender',
|
|
|
|
'birthday',
|
|
|
|
'barcode',
|
|
|
|
'password',
|
|
|
|
'tax_code',
|
|
|
|
'company',
|
|
|
|
'country',
|
|
|
|
'address',
|
|
|
|
'province',
|
|
|
|
'district',
|
|
|
|
'ward'
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* User Schema
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
User.init(
|
|
|
|
{
|
|
|
|
id: {
|
|
|
|
type: DataTypes.INTEGER,
|
|
|
|
autoIncrement: true,
|
|
|
|
primaryKey: true
|
|
|
|
},
|
|
|
|
type: {
|
|
|
|
type: DataTypes.STRING(10),
|
|
|
|
defaultValue: User.Types.INDIVIDUAL
|
|
|
|
},
|
|
|
|
name: {
|
|
|
|
type: DataTypes.STRING(155),
|
|
|
|
allowNull: false
|
|
|
|
},
|
|
|
|
note: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
phone: {
|
|
|
|
type: DataTypes.STRING(20),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
email: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
validate: { isEmail: true },
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
avatar: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
cover: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
birthday: {
|
|
|
|
type: DataTypes.DATE,
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
barcode: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
gender: {
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
values: values(User.Genders),
|
|
|
|
defaultValue: User.Genders.FEMALE
|
|
|
|
},
|
|
|
|
tax_code: {
|
|
|
|
type: DataTypes.STRING(25),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
company: {
|
|
|
|
type: DataTypes.STRING(100),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
country: {
|
|
|
|
type: DataTypes.STRING(3),
|
|
|
|
defaultValue: 'vn'
|
|
|
|
},
|
|
|
|
language: {
|
|
|
|
type: DataTypes.STRING(3),
|
|
|
|
default: 'vi'
|
|
|
|
},
|
|
|
|
timezone: {
|
|
|
|
type: DataTypes.INTEGER,
|
|
|
|
default: 7
|
|
|
|
},
|
|
|
|
address: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
province: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: null // id || name
|
|
|
|
},
|
|
|
|
district: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: null // id || name
|
|
|
|
},
|
|
|
|
ward: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: null // id || name
|
|
|
|
},
|
|
|
|
role: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: null // id | name
|
|
|
|
},
|
|
|
|
group: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: null // id | name
|
|
|
|
},
|
|
|
|
status: {
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
values: values(User.Statuses),
|
|
|
|
defaultValue: User.Statuses.ACTIVE
|
|
|
|
},
|
|
|
|
status_name: {
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
values: values(User.NameStatuses),
|
|
|
|
defaultValue: User.NameStatuses.ACTIVE
|
|
|
|
},
|
|
|
|
// stores: {
|
|
|
|
// type: DataTypes.JSONB,
|
|
|
|
// defaultValue: [] // id, name, phone, address
|
|
|
|
// },
|
|
|
|
service: {
|
|
|
|
type: DataTypes.STRING(50),
|
|
|
|
values: values(User.Services),
|
|
|
|
defaultValue: User.Services.USER
|
|
|
|
},
|
|
|
|
password: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: uuidv4()
|
|
|
|
},
|
|
|
|
permissions: {
|
|
|
|
type: DataTypes.ARRAY(DataTypes.STRING(155)),
|
|
|
|
defaultValue: [Permissions.USER]
|
|
|
|
},
|
|
|
|
|
|
|
|
// third party account
|
|
|
|
apple: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: {
|
|
|
|
id: null,
|
|
|
|
email: null,
|
|
|
|
name: null,
|
|
|
|
avatar: null,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
facebook: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: {
|
|
|
|
id: null,
|
|
|
|
email: null,
|
|
|
|
name: null,
|
|
|
|
avatar: null,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
google: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
defaultValue: {
|
|
|
|
id: null,
|
|
|
|
email: null,
|
|
|
|
name: null,
|
|
|
|
avatar: null,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
normalize_name: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
normalize_dob: {
|
|
|
|
type: DataTypes.INTEGER,
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
normalize_mob: {
|
|
|
|
type: DataTypes.INTEGER,
|
|
|
|
defaultValue: null
|
|
|
|
},
|
|
|
|
|
|
|
|
// metric
|
|
|
|
|
|
|
|
// config
|
|
|
|
is_active: {
|
|
|
|
type: DataTypes.BOOLEAN,
|
|
|
|
defaultValue: true
|
|
|
|
},
|
|
|
|
device_id: {
|
|
|
|
type: DataTypes.STRING(255),
|
|
|
|
defaultValue: 'unkown'
|
|
|
|
},
|
|
|
|
ip_address: {
|
|
|
|
type: DataTypes.STRING(12),
|
|
|
|
defaultValue: 'unkown'
|
|
|
|
},
|
|
|
|
is_verified_phone: {
|
|
|
|
type: DataTypes.BOOLEAN,
|
|
|
|
defaultValue: false
|
|
|
|
},
|
|
|
|
is_verified_email: {
|
|
|
|
type: DataTypes.BOOLEAN,
|
|
|
|
defaultValue: false
|
|
|
|
},
|
|
|
|
is_verified_password: {
|
|
|
|
type: DataTypes.BOOLEAN,
|
|
|
|
defaultValue: false
|
|
|
|
},
|
|
|
|
created_at: {
|
|
|
|
type: DataTypes.DATE,
|
|
|
|
defaultValue: () => new Date()
|
|
|
|
},
|
|
|
|
updated_at: {
|
|
|
|
type: DataTypes.DATE,
|
|
|
|
defaultValue: () => new Date()
|
|
|
|
},
|
|
|
|
created_by: {
|
|
|
|
type: DataTypes.JSONB,
|
|
|
|
allowNull: false,
|
|
|
|
defaultValue: {
|
|
|
|
id: null,
|
|
|
|
name: null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
timestamps: false,
|
|
|
|
sequelize: sequelize,
|
|
|
|
schema: serviceName,
|
|
|
|
tableName: 'tbl_users',
|
|
|
|
modelName: 'user'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register event emiter
|
|
|
|
*/
|
|
|
|
User.Events = {
|
|
|
|
USER_CREATED: `${serviceName}.user.created`,
|
|
|
|
USER_UPDATED: `${serviceName}.user.updated`,
|
|
|
|
USER_DELETED: `${serviceName}.user.deleted`,
|
|
|
|
};
|
|
|
|
User.EVENT_SOURCE = `${serviceName}.user`;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add your
|
|
|
|
* - pre-save hooks
|
|
|
|
* - validations
|
|
|
|
* - virtuals
|
|
|
|
*/
|
|
|
|
User.addHook('beforeCreate', async (model) => {
|
|
|
|
const user = model;
|
|
|
|
if (user.password) {
|
|
|
|
const rounds = 10;
|
|
|
|
user.password = await hash(user.password, rounds);
|
|
|
|
console.log("pass created");
|
|
|
|
}
|
|
|
|
|
|
|
|
return user;
|
|
|
|
});
|
|
|
|
|
|
|
|
User.addHook('beforeUpdate', async (model, options) => {
|
|
|
|
const user = model;
|
|
|
|
const { password } = options;
|
|
|
|
// has pass
|
|
|
|
if (password) {
|
|
|
|
const rounds = 10;
|
|
|
|
user.password = await hash(password, rounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
return user;
|
|
|
|
});
|
|
|
|
|
|
|
|
User.addHook('afterCreate', () => { });
|
|
|
|
|
|
|
|
User.addHook('afterUpdate', () => { });
|
|
|
|
|
|
|
|
User.addHook('afterDestroy', () => { });
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check min or max in condition
|
|
|
|
* @param {*} options
|
|
|
|
* @param {*} field
|
|
|
|
* @param {*} type
|
|
|
|
*/
|
|
|
|
function checkMinMaxOfConditionFields(options, field, type = 'Number') {
|
|
|
|
let _min = null;
|
|
|
|
let _max = null;
|
|
|
|
|
|
|
|
// Transform min max
|
|
|
|
if (type === 'Date') {
|
|
|
|
_min = new Date(options[`min_${field}`]);
|
|
|
|
_min.setHours(0, 0, 0, 0);
|
|
|
|
|
|
|
|
_max = new Date(options[`max_${field}`]);
|
|
|
|
_max.setHours(23, 59, 59, 999);
|
|
|
|
}
|
|
|
|
if (type === 'Number') {
|
|
|
|
_min = parseFloat(options[`min_${field}`]);
|
|
|
|
_max = parseFloat(options[`max_${field}`]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transform condition
|
|
|
|
if (
|
|
|
|
!isNil(options[`min_${field}`]) ||
|
|
|
|
!isNil(options[`max_${field}`])
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
options[`min_${field}`] &&
|
|
|
|
!options[`max_${field}`]
|
|
|
|
) {
|
|
|
|
options[field] = {
|
|
|
|
[Op.gte]: _min
|
|
|
|
};
|
|
|
|
} else if (
|
|
|
|
!options[`min_${field}`] &&
|
|
|
|
options[`max_${field}`]
|
|
|
|
) {
|
|
|
|
options[field] = {
|
|
|
|
[Op.lte]: _max
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
options[field] = {
|
|
|
|
[Op.gte]: _min || 0,
|
|
|
|
[Op.lte]: _max || 0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove first condition
|
|
|
|
delete options[`max_${field}`];
|
|
|
|
delete options[`min_${field}`];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load query
|
|
|
|
* @param {*} params
|
|
|
|
*/
|
|
|
|
function filterConditions(params) {
|
|
|
|
const options = omitBy(params, isNil);
|
|
|
|
options.is_active = true;
|
|
|
|
|
|
|
|
if (options.types) {
|
|
|
|
options.type = { [Op.in]: options.types.split(',') };
|
|
|
|
}
|
|
|
|
delete options.types;
|
|
|
|
|
|
|
|
if (options.roles) {
|
|
|
|
options['role.id'] = { [Op.in]: options.roles.split(',') };
|
|
|
|
}
|
|
|
|
delete options.roles;
|
|
|
|
|
|
|
|
if (options.groups) {
|
|
|
|
options['group.id'] = { [Op.in]: options.groups.split(',') };
|
|
|
|
}
|
|
|
|
delete options.groups;
|
|
|
|
|
|
|
|
if (options.statuses) {
|
|
|
|
options.status = { [Op.in]: options.statuses.split(',') };
|
|
|
|
}
|
|
|
|
delete options.statuses;
|
|
|
|
|
|
|
|
if (options.services) {
|
|
|
|
options.service = { [Op.in]: options.services.split(',') };
|
|
|
|
}
|
|
|
|
delete options.services;
|
|
|
|
|
|
|
|
// console.log("delete services from asfsd");
|
|
|
|
|
|
|
|
if (options.genders) {
|
|
|
|
options.gender = { [Op.in]: options.genders.split(',') };
|
|
|
|
}
|
|
|
|
delete options.genders;
|
|
|
|
|
|
|
|
if (options.staffs) {
|
|
|
|
options['created_by.id'] = { [Op.in]: options.staffs.split(',') };
|
|
|
|
}
|
|
|
|
delete options.staffs;
|
|
|
|
|
|
|
|
if (options.stores) {
|
|
|
|
options.store_path = { [Op.overlap]: options.stores.split(',') };
|
|
|
|
}
|
|
|
|
delete options.stores;
|
|
|
|
|
|
|
|
if (options.provinces) {
|
|
|
|
options['province.id'] = { [Op.in]: options.provinces.split(',') };
|
|
|
|
}
|
|
|
|
delete options.provinces;
|
|
|
|
|
|
|
|
if (options.keyword) {
|
|
|
|
options.normalize_name = { [Op.iLike]: `%${options.keyword}%` };
|
|
|
|
}
|
|
|
|
delete options.keyword;
|
|
|
|
|
|
|
|
// Date Filter
|
|
|
|
checkMinMaxOfConditionFields(options, 'created_at', 'Date');
|
|
|
|
|
|
|
|
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load sort query
|
|
|
|
* @param {*} sort_by
|
|
|
|
* @param {*} order_by
|
|
|
|
*/
|
|
|
|
function sortConditions({ sort_by, order_by }) {
|
|
|
|
let sort = null;
|
|
|
|
switch (sort_by) {
|
|
|
|
case 'created_at':
|
|
|
|
sort = ['created_at', order_by];
|
|
|
|
break;
|
|
|
|
case 'updated_at':
|
|
|
|
sort = ['updated_at', order_by];
|
|
|
|
break;
|
|
|
|
case 'total_debt':
|
|
|
|
sort = ['total_debt', order_by];
|
|
|
|
break;
|
|
|
|
case 'total_point':
|
|
|
|
sort = ['total_point', order_by];
|
|
|
|
break;
|
|
|
|
case 'total_purchase':
|
|
|
|
sort = ['total_purchase', order_by];
|
|
|
|
break;
|
|
|
|
case 'total_invoice_price':
|
|
|
|
sort = ['total_invoice_price', order_by];
|
|
|
|
break;
|
|
|
|
case 'total_return_price':
|
|
|
|
sort = ['total_return_price', order_by];
|
|
|
|
break;
|
|
|
|
default: sort = ['created_at', 'DESC'];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return sort;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Transform postgres model to expose object
|
|
|
|
*/
|
|
|
|
User.transform = (params, includeRestrictedFields = true) => {
|
|
|
|
const transformed = {};
|
|
|
|
const fields = [
|
|
|
|
'id',
|
|
|
|
'name',
|
|
|
|
'note',
|
|
|
|
'phone',
|
|
|
|
'email',
|
|
|
|
'avatar',
|
|
|
|
'role',
|
|
|
|
'cover',
|
|
|
|
'gender',
|
|
|
|
'birthday',
|
|
|
|
'barcode',
|
|
|
|
'tax_code',
|
|
|
|
'company',
|
|
|
|
'country',
|
|
|
|
'address',
|
|
|
|
'province',
|
|
|
|
'district',
|
|
|
|
'ward',
|
|
|
|
|
|
|
|
// manager
|
|
|
|
'is_active',
|
|
|
|
'is_verified_phone',
|
|
|
|
'is_verified_email',
|
|
|
|
'is_verified_password'
|
|
|
|
];
|
|
|
|
if (includeRestrictedFields) {
|
|
|
|
const privateFiles = [
|
|
|
|
'type',
|
|
|
|
'role',
|
|
|
|
'group',
|
|
|
|
'stores',
|
|
|
|
'status',
|
|
|
|
'status_name',
|
|
|
|
'permissions',
|
|
|
|
'created_by'
|
|
|
|
];
|
|
|
|
fields.push(...privateFiles);
|
|
|
|
};
|
|
|
|
|
|
|
|
// console.log(fields + "@@@");
|
|
|
|
|
|
|
|
fields.forEach((field) => {
|
|
|
|
transformed[field] = params[field];
|
|
|
|
});
|
|
|
|
|
|
|
|
// pipe decimal
|
|
|
|
const decimalFields = [
|
|
|
|
'total_debt',
|
|
|
|
'total_order_price',
|
|
|
|
'total_invoice_price',
|
|
|
|
'total_return_price',
|
|
|
|
'total_purchase',
|
|
|
|
'total_point',
|
|
|
|
];
|
|
|
|
decimalFields.forEach((field) => {
|
|
|
|
transformed[field] = parseInt(params[field], 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
// pipe date
|
|
|
|
const dateFields = [
|
|
|
|
'birthday',
|
|
|
|
'created_at',
|
|
|
|
'updated_at',
|
|
|
|
'last_purchase'
|
|
|
|
];
|
|
|
|
dateFields.forEach((field) => {
|
|
|
|
transformed[field] = moment(params[field]).unix();
|
|
|
|
});
|
|
|
|
// console.log(transformed);
|
|
|
|
|
|
|
|
return transformed;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all changed properties
|
|
|
|
*/
|
|
|
|
User.getChangedProperties = ({ newModel, oldModel }, includeRestrictedFields = true) => {
|
|
|
|
const changedProperties = [];
|
|
|
|
const allChangableProperties = [
|
|
|
|
'name',
|
|
|
|
'note',
|
|
|
|
'phone',
|
|
|
|
'email',
|
|
|
|
'avatar',
|
|
|
|
'cover',
|
|
|
|
'gender',
|
|
|
|
'birthday',
|
|
|
|
'barcode',
|
|
|
|
'tax_code',
|
|
|
|
'company',
|
|
|
|
'country',
|
|
|
|
'address',
|
|
|
|
'province',
|
|
|
|
'district',
|
|
|
|
'ward',
|
|
|
|
];
|
|
|
|
if (includeRestrictedFields) {
|
|
|
|
const privateFiles = [
|
|
|
|
'type',
|
|
|
|
'role',
|
|
|
|
'group',
|
|
|
|
'password',
|
|
|
|
'stores',
|
|
|
|
'status',
|
|
|
|
'permissions'
|
|
|
|
];
|
|
|
|
allChangableProperties.push(...privateFiles);
|
|
|
|
// console.log(allChangableProperties);
|
|
|
|
}
|
|
|
|
if (!oldModel) {
|
|
|
|
// console.log("old model");
|
|
|
|
return allChangableProperties;
|
|
|
|
}
|
|
|
|
|
|
|
|
allChangableProperties.forEach((field) => {
|
|
|
|
if (!isEqual(newModel[field], oldModel[field])) {
|
|
|
|
changedProperties.push(field);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return changedProperties;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check user by phone - email
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @param {object} data email || phone
|
|
|
|
*/
|
|
|
|
User.getUserByPhoneOrEmail = async ({ phone, email, validate = false }) => {
|
|
|
|
try {
|
|
|
|
let user = null;
|
|
|
|
if (phone) {
|
|
|
|
user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
is_active: true,
|
|
|
|
phone: phone
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (email) {
|
|
|
|
user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
is_active: true,
|
|
|
|
email: email,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (!user) {
|
|
|
|
throw new APIError({
|
|
|
|
status: httpStatus.NOT_FOUND,
|
|
|
|
message: 'Không tìm thấy tài khoản này!'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (validate) {
|
|
|
|
if (user.status === User.Statuses.INACTIVE) {
|
|
|
|
throw new APIError({
|
|
|
|
message: 'Tài khoản chưa được kích hoạt!',
|
|
|
|
status: httpStatus.UNAUTHORIZED
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (user.status === User.Statuses.BANNED) {
|
|
|
|
throw new APIError({
|
|
|
|
message: 'Tài khoản đã bị khoá!',
|
|
|
|
status: httpStatus.UNAUTHORIZED
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return user;
|
|
|
|
} catch (ex) {
|
|
|
|
throw (ex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
User.getUserByPhoneOrEmailRegister = async ({ phone, email }) => {
|
|
|
|
try {
|
|
|
|
let user = null;
|
|
|
|
if (phone) {
|
|
|
|
user = await User.find({
|
|
|
|
where: {
|
|
|
|
is_active: true,
|
|
|
|
phone: phone
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (email) {
|
|
|
|
user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
is_active: true,
|
|
|
|
email: email,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (user) {
|
|
|
|
throw new APIError({
|
|
|
|
status: httpStatus.NOT_FOUND,
|
|
|
|
message: 'Tài khoản này đã được đăng kí'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// if (validate) {
|
|
|
|
// if (user.status === User.Statuses.INACTIVE) {
|
|
|
|
// throw new APIError({
|
|
|
|
// message: 'Tài khoản chưa được kích hoạt!',
|
|
|
|
// status: httpStatus.UNAUTHORIZED
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
// if (user.status === User.Statuses.BANNED) {
|
|
|
|
// throw new APIError({
|
|
|
|
// message: 'Tài khoản đã bị khoá!',
|
|
|
|
// status: httpStatus.UNAUTHORIZED
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
return true;
|
|
|
|
} catch (ex) {
|
|
|
|
throw (ex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
User.getStaffPermissions = async (staffId) => {
|
|
|
|
const user = await User.findOne({
|
|
|
|
attributes: ['permissions'],
|
|
|
|
where: {
|
|
|
|
status: User.Statuses.ACTIVE,
|
|
|
|
is_active: true,
|
|
|
|
id: staffId
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return user.permissions;
|
|
|
|
};
|
|
|
|
/**
|
|
|
|
* Get Store By Id
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @param {String} userId
|
|
|
|
*/
|
|
|
|
User.get = async (userId) => {
|
|
|
|
try {
|
|
|
|
const user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
id: userId,
|
|
|
|
is_active: true
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// console.log(user);
|
|
|
|
if (isNil(user)) {
|
|
|
|
throw new APIError({
|
|
|
|
status: httpStatus.NOT_FOUND,
|
|
|
|
message: 'Không tìm thấy người dùng này!'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return user;
|
|
|
|
} catch (ex) {
|
|
|
|
throw (ex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List
|
|
|
|
*
|
|
|
|
* @param {number} skip - Number of records to be skipped.
|
|
|
|
* @param {number} limit - Limit number of records to be returned.
|
|
|
|
* @returns {Promise<Store[]>}
|
|
|
|
*/
|
|
|
|
User.list = async ({
|
|
|
|
roles,
|
|
|
|
types,
|
|
|
|
groups,
|
|
|
|
stores,
|
|
|
|
provinces,
|
|
|
|
staffs,
|
|
|
|
genders,
|
|
|
|
statuses,
|
|
|
|
services,
|
|
|
|
keyword,
|
|
|
|
min_created_at,
|
|
|
|
max_created_at,
|
|
|
|
min_last_purchase,
|
|
|
|
max_last_purchase,
|
|
|
|
min_total_order_price,
|
|
|
|
max_total_order_price,
|
|
|
|
min_total_invoice_price,
|
|
|
|
max_total_invoice_price,
|
|
|
|
min_total_point,
|
|
|
|
max_total_point,
|
|
|
|
min_total_debt,
|
|
|
|
max_total_debt,
|
|
|
|
|
|
|
|
// sort condition
|
|
|
|
skip = 0,
|
|
|
|
limit = 20,
|
|
|
|
sort_by = 'desc',
|
|
|
|
order_by = 'created_at',
|
|
|
|
}) => {
|
|
|
|
const options = filterConditions({
|
|
|
|
roles,
|
|
|
|
types,
|
|
|
|
groups,
|
|
|
|
stores,
|
|
|
|
provinces,
|
|
|
|
staffs,
|
|
|
|
genders,
|
|
|
|
statuses,
|
|
|
|
services,
|
|
|
|
keyword,
|
|
|
|
min_created_at,
|
|
|
|
max_created_at,
|
|
|
|
min_last_purchase,
|
|
|
|
max_last_purchase,
|
|
|
|
min_total_order_price,
|
|
|
|
max_total_order_price,
|
|
|
|
min_total_invoice_price,
|
|
|
|
max_total_invoice_price,
|
|
|
|
min_total_point,
|
|
|
|
max_total_point,
|
|
|
|
min_total_debt,
|
|
|
|
max_total_debt,
|
|
|
|
});
|
|
|
|
|
|
|
|
const sorts = sortConditions({
|
|
|
|
sort_by,
|
|
|
|
order_by
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return User.findAll({
|
|
|
|
where: options,
|
|
|
|
order: [sorts],
|
|
|
|
offset: skip,
|
|
|
|
limit: limit
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Total records.
|
|
|
|
*
|
|
|
|
* @param {number} skip - Number of records to be skipped.
|
|
|
|
* @param {number} limit - Limit number of records to be returned.
|
|
|
|
* @returns {Promise<Number>}
|
|
|
|
*/
|
|
|
|
User.totalRecords = ({
|
|
|
|
roles,
|
|
|
|
types,
|
|
|
|
groups,
|
|
|
|
stores,
|
|
|
|
provinces,
|
|
|
|
staffs,
|
|
|
|
genders,
|
|
|
|
statuses,
|
|
|
|
services,
|
|
|
|
keyword,
|
|
|
|
min_created_at,
|
|
|
|
max_created_at,
|
|
|
|
min_last_purchase,
|
|
|
|
max_last_purchase,
|
|
|
|
min_total_order_price,
|
|
|
|
max_total_order_price,
|
|
|
|
min_total_invoice_price,
|
|
|
|
max_total_invoice_price,
|
|
|
|
min_total_point,
|
|
|
|
max_total_point,
|
|
|
|
min_total_debt,
|
|
|
|
max_total_debt,
|
|
|
|
}) => {
|
|
|
|
const options = filterConditions({
|
|
|
|
roles,
|
|
|
|
types,
|
|
|
|
groups,
|
|
|
|
stores,
|
|
|
|
provinces,
|
|
|
|
staffs,
|
|
|
|
genders,
|
|
|
|
statuses,
|
|
|
|
services,
|
|
|
|
keyword,
|
|
|
|
min_created_at,
|
|
|
|
max_created_at,
|
|
|
|
min_last_purchase,
|
|
|
|
max_last_purchase,
|
|
|
|
min_total_order_price,
|
|
|
|
max_total_order_price,
|
|
|
|
min_total_invoice_price,
|
|
|
|
max_total_invoice_price,
|
|
|
|
min_total_point,
|
|
|
|
max_total_point,
|
|
|
|
min_total_debt,
|
|
|
|
max_total_debt,
|
|
|
|
});
|
|
|
|
|
|
|
|
return User.count({ where: options });
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check Duplicate Account Info
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @param {object} data email || phone
|
|
|
|
*/
|
|
|
|
User.checkDuplicate = async ({ userId, service, email, phone }) => {
|
|
|
|
let user = null;
|
|
|
|
let message = null;
|
|
|
|
|
|
|
|
if (phone) {
|
|
|
|
user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
phone,
|
|
|
|
service,
|
|
|
|
is_active: true,
|
|
|
|
id: userId ? { [Op.ne]: userId } : { [Op.ne]: null }
|
|
|
|
}
|
|
|
|
});
|
|
|
|
message = '"Phone already in use';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (email) {
|
|
|
|
user = await User.findOne({
|
|
|
|
where: {
|
|
|
|
email,
|
|
|
|
service,
|
|
|
|
is_active: true,
|
|
|
|
id: userId ? { [Op.ne]: userId } : { [Op.ne]: null }
|
|
|
|
}
|
|
|
|
});
|
|
|
|
message = '"Email" already exist';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (user) {
|
|
|
|
throw new APIError({
|
|
|
|
message: message,
|
|
|
|
errors: [
|
|
|
|
{
|
|
|
|
field: 'username',
|
|
|
|
location: 'body',
|
|
|
|
messages: [message]
|
|
|
|
}
|
|
|
|
],
|
|
|
|
status: httpStatus.CONFLICT
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compare password
|
|
|
|
*
|
|
|
|
* @param {String} password
|
|
|
|
* @param {User} model
|
|
|
|
*/
|
|
|
|
User.passwordMatches = async (model, password) => compare(password, model.password);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if current user is expired
|
|
|
|
*
|
|
|
|
* @param {Date} currentTime
|
|
|
|
* @param {User} model
|
|
|
|
* @returns {Boolean}
|
|
|
|
*/
|
|
|
|
User.isExpired = (model, currentTime = null) => {
|
|
|
|
const currentTimeToCheck = currentTime !== null ? currentTime : new Date();
|
|
|
|
return currentTimeToCheck >= model.expired_at;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filter only allowed fields from user
|
|
|
|
*
|
|
|
|
* @param {Object} params
|
|
|
|
*/
|
|
|
|
User.filterParams = (params, includeRestrictedFields = true) => {
|
|
|
|
if (includeRestrictedFields) {
|
|
|
|
return pick(params, PUBLIC_FIELDS);
|
|
|
|
}
|
|
|
|
return pick(params, PUBLIC_PROFILE_FIELDS);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return fully qualified phone number
|
|
|
|
*
|
|
|
|
* @param {String} phone
|
|
|
|
*/
|
|
|
|
User.normalizePhoneNumber = (phone) =>
|
|
|
|
`+84${phone
|
|
|
|
.replace(/\D/g, '')
|
|
|
|
.replace(/^84/, '')
|
|
|
|
.replace(/^0*/, '')}`;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef User
|
|
|
|
*/
|
|
|
|
export default User;
|