HEX
Server: nginx/1.24.0
System: Linux nowruzgan 6.8.0-57-generic #59-Ubuntu SMP PREEMPT_DYNAMIC Sat Mar 15 17:40:59 UTC 2025 x86_64
User: babak (1000)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: //var/dev/farhangmoaser/web/routes/api/1.0/user.js
/**
 * Express endpoints for /user address
 * Version: 0.1
 * Author: Babak Vandad
 *
 * Restful api for these addresses:
 * 		GET			/user/role
 * 		PUT			/user/role
 * 		POST		/user/role/:id
 * 		DELETE		/user/role/:id
 * 		GET			/user/access/actions
 * 		GET			/user/access/rebuild
 * 		GET			/user/access
 * 		GET			/user/access/:role
 * 		GET			/user/access/:action
 * 		GET			/user/access/:action/:role
 * 		PUT			/user/access/:action/:role
 * 		DELETE		/user/access/:action/:role
 * 		GET			/user/me
 * 		GET			/user/:email
 * 		GET			/user
 * 		PUT			/user
 * 		PUT			/user/me
 * 		POST		/user/me
 * 		POST		/user/:email
 * 		DELETE		/user/:email
 */

var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require('fs');
var glob = require('glob');
var md5 = require('md5');
var consts = require(path.join(BASEDIR, 'consts'));
var authHelper = require(path.join(BASEDIR, 'helpers/auth'));
var mailHelper = require(path.join(BASEDIR, 'helpers/mail'));
var UserModel = require(path.join(BASEDIR, 'models/user'));
var UserAccessModel = require(path.join(BASEDIR, 'models/userAccess'));
var UserRoleModel = require(path.join(BASEDIR, 'models/userRole'));
var db = require(path.join(BASEDIR, 'connectors/mysql'));

var authenticate = authHelper.authenticate;
var access = authHelper.access;

/**
 * Fail a request by releasing database connection, rollback transaction
 * and sending the proper error code and message with 4xx or 5xx status codes.
 * @param  {object}		express response object
 * @param  {object}		db connection
 * @param  {object}		error object from costs.js
 * @param  {integer}	override status code (if you want to send 200 instead of 4xx/5xx)
 * @return {function}	the function to fail the request.
 */
var fail = function(response, conn, error, status){
	return function(err){
		if(conn && conn.release)
			conn.release();
		response.status(status ? status : error.status).json({error: error.code, message: error.message});
	};
};

/**
 * Sends data to the client, commits transaction and releases the db connection
 * @param  {object}		express response object
 * @param  {object}		db connection
 * @param  {object}		data for the client
 */
var send = function(res, conn, data){
	if(conn) conn.release();

	if(data) res.json(data);
	else res.end();
};

/**
 * makes a searchable string (does not remove spaces)
 * @param  {string} str user input or given entry title
 * @return {string}     searchable string
 */
var simplifyString = function(str){
	return str
		.trim()
		.toLowerCase()
		.replace(/[\t'\-!() ]/g, '')
		.replace(/ +/g, ' ')
		.replace(/[ًٌٍَُِّْـ.]/g, '')
		.replace(/ي/g, 'ی')
		.replace(/ى/g, 'ی')
		.replace(/ك/g, 'ک')
		.replace(/[àâä]/g, 'a')
		.replace(/æ/g, 'ae')
		.replace(/[îï]/g, 'i')
		.replace(/[éèêë]/g, 'e')
		.replace(/[ôö]/g, 'o')
		.replace(/œ/g, 'oe')
		.replace(/[ùûü]/g, 'u')
		.replace(/ç/g, 'c')
		.replace(/ñ/g, 'n')
		.replace(/ÿ/g, 'y')
		;
};

/* list all roles */
router.get('/role', authenticate, access('user:role:get', 'نمایش فهرست نقش‌ها'), function(req, res, next){
	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		UserRoleModel.list(conn).then(
			function(roles){
				send(res, conn, {list: roles, sum: roles.length});
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* add a new role */
router.put('/role', authenticate, access('user:role:put', 'افزودن نقش'), function(req, res, next){
	if(!req.body.title)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	var roletitle = req.body.title;

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		(new UserRoleModel(conn, {title: roletitle})).save().then(
			function(role){
				send(res, conn, {id: role.field('id'), title: role.field('title')});
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* update an existing role */
router.post('/role/:id', authenticate, access('user:role:post', 'ویرایش عنوان نقش'), function(req, res, next){
	if(!req.params.id)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	var roleid = parseInt(req.params.id);
	if(isNaN(roleid))
		return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	var record = {id: roleid};
	if(req.body.hasOwnProperty('title'))
		record.title = req.body.title;

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		(new UserRoleModel(conn, record)).save().then(
			function(user){
				send(res, conn, user.fields());
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* delete an existing role */
router.delete('/role/:id', authenticate, access('user:role:delete', 'حذف نقش'), function(req, res, next){
	if(!req.params.id)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();
	if(!req.body.hasOwnProperty('substitute') && !req.query.hasOwnProperty('substitute'))
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	var roleid = parseInt(req.params.id);
	if(isNaN(roleid))
		return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
	if(roleid===consts.v.ROLE_ADMIN) return fail(res, null, consts.e.ERR_DATA_CONFLICT)();
	if(roleid===consts.v.ROLE_UNAUTHENTICATED) return fail(res, null, consts.e.ERR_DATA_CONFLICT)();
	var record = {id: roleid};

	var substituteid = NaN;
	if(req.body.hasOwnProperty('substitute'))
		var substituteid = parseInt(req.body.substitute);
	else if(req.query.hasOwnProperty('substitute'))
		var substituteid = parseInt(req.query.substitute);
	if(isNaN(substituteid))
		return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		UserRoleModel.reRole(conn, roleid, substituteid).then(
			function(){
				(new UserRoleModel(conn, {id: substituteid})).save().then(
					function(){
						(new UserRoleModel(conn, {id: roleid})).remove().then(
							function(){
								send(res, conn, {id: roleid});
							}, fail(res, conn, consts.e.ERR_DB_ERROR));
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* list all actions */
router.get('/access/actions', authenticate, access('user:access:get-actions', 'نمایش فهرست همه کارهایی که نقش‌های مختلف می‌توانند انجام دهند'), function(req, res, next){
	var actions = UserAccessModel.listActions();
	send(res, null, {list: actions, sum: actions.length});
});

/* rebuild rules cache */
router.get('/access/rebuild', authenticate, access('user:access:get-rebuild', 'بازسازی cache قواعد دسترسی'), function(req, res, next){
	UserAccessModel.rebuild().then(
		function(){
			var actions = UserAccessModel.listActions();
			send(res, null, {list: actions, sum: actions.length});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* list all access rules */
router.get('/access', authenticate, access('user:access:get', 'نمایش فهرست قواعد دسترسی'), function(req, res, next){
	UserAccessModel.list(false, false).then(
		function(rows){
			send(res, null, {list: rows, sum: rows.length});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* list access rules by role */
router.get('/access/:role', authenticate, access('user:access:get-role', 'نمایش فهرست کارهایی که یک نقش می‌تواند انجام دهد'), function(req, res, next){
	if(!/^\d+$/.exec(req.params.role)) return next();
	var role = parseInt(req.params.role);

	UserAccessModel.list(null, role).then(
		function(rows){
			send(res, null, {list: rows, sum: rows.length});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* list access rules by action */
router.get('/access/:action', authenticate, access('user:access:get-action', 'نمایش فهرست نقش‌هایی که می‌توانند یک کار را انجام دهند'), function(req, res, next){
	var action = req.params.action;

	if(!/.*:.*/.exec(action)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	UserAccessModel.list(action, false).then(
		function(rows){
			send(res, null, {list: rows, sum: rows.length});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* check an access by (action, role) pair */
router.get('/access/:action/:role', authenticate, access('user:access:get-action-role', 'استعلام امکان انجام یک کار مشخص توسط یک نقش مشخص'), function(req, res, next){
	var action = req.params.action;
	var role = parseInt(req.params.role);

	if(isNaN(role)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
	if(!/.*:.*/.exec(action)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	if(role===1) return send(res, null, {result: true});

	UserAccessModel.check(action, role).then(
		function(permits){
			send(res, null, {result: permits});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* add an access rule */
router.put('/access/:action/:role', authenticate, access('user:access:put-action-role', 'صدور مجوز انجام یک کار مشخص برای یک نقش مشخص'), function(req, res, next){
	var action = req.params.action;
	var role = parseInt(req.params.role);

	if(isNaN(role)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
	if(!/.*:.*/.exec(action)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	if(role===1) return fail(res, null, consts.e.ERR_DATA_CONFLICT)();

	(new UserAccessModel({action: action, role: role})).save().then(
		function(){
			send(res, null, {action: action, role: role});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* delete an access rule */
router.delete('/access/:action/:role', authenticate, access('user:access:delete-action-role', 'منع انجام یک کار مشخص برای یک نقش مشخص'), function(req, res, next){
	var action = req.params.action;
	var role = parseInt(req.params.role);

	if(isNaN(role)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
	if(!/.*:.*/.exec(action)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();

	if(role===1) return fail(res, null, consts.e.ERR_DATA_CONFLICT)();

	(new UserAccessModel({action: action, role: role})).remove().then(
		function(){
			send(res, null, {action: action, role: role});
		}, fail(res, null, consts.e.ERR_DB_ERROR));
});

/* user gets his/her own data */
router.get('/me', authenticate, access('user:get-me', 'کاربر اطلاعات خودش را مشاهده کند'), function(req, res, next){
	if(!req.user || !req.isAuthenticated()) return fail(res, null, consts.e.ERR_AUTH)();
	
	var userData = req.user.fields();
	delete userData.id;
	if(userData.userType == consts.v.USER_TYPE_PEOPLE)
		delete userData.key;
	delete userData.searchable;
	delete userData.sortable;
	delete userData.password;
	send(res, null, req.user.fields());
});

/* load user by email */
router.get('/:email', authenticate, access('user:get-email', 'مشاهده اطلاعات یک کاربر'), function(req, res, next){
	if(!req.params.email)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		var userModel = new UserModel(conn, {email: req.params.email});
		userModel.load().then(
			function(user) {
				if(!user) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
				var userData = user.fields();
				delete userData.id;
				delete userData.password;
				userData.hitData = user.hitData;
				send(res, conn, userData);
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* reset user's hit counter */
router.get('/:email/reset-hits', authenticate, access('user:reset-hits', 'بازنشانی شمارنده بازدیدها'), function(req, res, next){
	if(!req.params.email)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	db.getConnection((err, conn) => {
		if(err) return fail(res, conn, err)();

		var userModel = new UserModel(conn, {email: req.params.email});
		userModel.load().then(
			user => {
				if(!user) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
				user.resetHitCounter().then(
					() => {
						let userData = user.fields();
						delete userData.id;
						delete userData.password;
						userData.hitData = user.hitData;
						send(res, conn, userData);
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* list users */
router.get('/', authenticate, access('user:get', 'نمایش فهرست کاربران'), function(req, res, next){
	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		var query = {};

		if(req.query.hasOwnProperty('email')) query.email = req.query.email.toLowerCase();
		if(req.query.hasOwnProperty('minDate')) query['>=created'] = parseInt(req.query.minDate);
		if(req.query.hasOwnProperty('maxDate')) query['<=created'] = parseInt(req.query.maxDate);
		if(req.query.hasOwnProperty('state')) query.state = parseInt(req.query.state);
		if(req.query.hasOwnProperty('role')) query.role = parseInt(req.query.role);
		if(req.query.hasOwnProperty('userType')) query.userType = parseInt(req.query.userType);
		// TODO: support quota full
		// if(req.query.hasOwnProperty('quotaFull')) 

		if(req.query.hasOwnProperty('displayname'))
			query['%%searchable'] = '%'+simplifyString(req.query.displayname)+'%';

		if(req.query.hasOwnProperty('email') && (/^%/.exec(req.query.email) || /%$/.exec(req.query.email))){
			delete query.email;
			query['%%email'] = simplifyString(req.query.email);
		}

		var limit = req.query.hasOwnProperty('limit') ? Math.min(100, parseInt(req.query.limit)) : 100;
		var page = req.query.hasOwnProperty('page') ? parseInt(req.query.page) : 0;
		var offset = page*limit;

		// list of records based on criteria
		var listQuery = function(records) {
			records.map(function(record){
				delete record.id;
			});
			var response = [];
			return UserModel.count(conn, query).then(
				function(sum) {
					send(res, conn, {sum: sum, list: records});
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		};

		UserModel.list(conn, query, limit, offset, req.query.orderBy).then(
			listQuery, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* add a new user */
router.put('/', authenticate, access('user:put', 'افزودن کاربر'), function(req, res, next) {
	if(!req.body.email || !req.body.role)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	var data = {
		email: req.body.email.toLowerCase(),
		displayname: '',
		searchable: '',
		sortable: ''
	};

	if(req.body.key) data.key = parseInt(req.body.key);
	if(req.body.displayname) data.displayname = req.body.displayname;
	else if(req.body.firstname || req.body.lastname) data.displayname = (req.body.firstname + ' ' + req.body.lastname).trim();
	data.searchable = simplifyString(data.displayname);
	if(req.body.sortable) data.sortable = req.body.sortable;
	else if(req.body.firstname || req.body.lastname) data.sortable = simplifyString(req.body.lastname + ' ' + req.body.firstname);
	if(req.body.userType) data.userType = parseInt(req.body.userType);
	if(req.body.role) data.role = parseInt(req.body.role);
	if(req.body.firstname) data.firstname = req.body.firstname;
	if(req.body.lastname) data.lastname = req.body.lastname;
	if(req.body.gender) data.gender = parseInt(req.body.gender);
	if(req.body.password) data.password = md5(req.body.password);
	if(req.body.avatar) data.avatar = req.body.avatar;
	if(req.body.hitCount) data.hitCount = parseInt(req.body.hitCount);
	if(req.body.hitLimit) data.hitLimit = parseInt(req.body.hitLimit);
	if(req.body.hitPeriod) data.hitPeriod = parseInt(req.body.hitPeriod);
	if(req.body.state) data.state = parseInt(req.body.state);

	if(data.role && data.role==consts.v.ROLE_UNAUTHENTICATED) return fail(res, null, consts.e.ERR_DATA_CONFLICT)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		var addUser = function(){
			(new UserModel(conn, {email: data.email})).load().then(
				function(duplicateUser) {
					if(duplicateUser) return fail(res, conn, consts.e.ERR_DUPLICATE_RECORD)();

					var userModel = new UserModel(conn, data);
					userModel.save().then(
						function(user) {
							(new UserRoleModel(conn, {id: user.field('role')})).save().then(
								function(){
									var userData = user.fields();
									delete userData.id;
									userData.hitData = user.hitData;
									send(res, conn, userData);
								}, fail(res, conn, consts.e.ERR_DB_ERROR));
						}, fail(res, conn, consts.e.ERR_DB_ERROR));
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		};

		if(req.body.avatar)
			fs.rename(path.join(BASEDIR, 'private/temp/'+req.body.avatar), path.join(BASEDIR, 'private/avatar/'+req.body.avatar), addUser);
		else
			addUser();
	});
});

/* user creates an account. (no authentication needed) */
router.put('/me', authenticate, access('user:put-me', 'عضویت کاربر ناشناس'), function(req, res, next) {
	if(!req.body.email || !req.body.password)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	if(!/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.exec(req.body.email))
		return fail(res, null, consts.e.ERR_MALFORMED_REQUEST);

	var data = {
		email: req.body.email.toLowerCase(),
		displayname: '',
		searchable: '',
		sortable: '',
		userType: consts.v.USER_TYPE_PEOPLE,
		role: consts.v.ROLE_SERVICE_USER,
		state: consts.v.STATE_INACTIVE,
		hitCount: 0,
		hitLimit: 300,
		hitPeriod: 1
	};

	req.body.firstname = (req.body.firstname || '').trim();
	req.body.lastname = (req.body.lastname || '').trim();
	data.displayname = req.body.firstname + ' ' + req.body.lastname
	data.searchable = simplifyString(data.displayname);
	data.sortable = simplifyString(req.body.lastname + ' ' + req.body.firstname);
	if(req.body.firstname) data.firstname = req.body.firstname;
	if(req.body.lastname) data.lastname = req.body.lastname;
	if(req.body.gender) data.gender = parseInt(req.body.gender);
	if(req.body.password) data.password = md5(req.body.password);

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, consts.e.ERR_DB_ERROR)();

		var addUser = function(){
			(new UserModel(conn, {email: data.email})).load().then(
				function(duplicateUser) {
					if(duplicateUser) return fail(res, conn, consts.e.ERR_DUPLICATE_RECORD)();

					var userModel = new UserModel(conn, data);
					userModel.generateActivationKey();
					userModel.save().then(
						function(user) {
							(new UserRoleModel(conn, {id: user.field('role')})).save().then(
								function(){
									var userData = user.fields();
									mailHelper.sendActivationEMail(user).then(
										info => {
											// console.log(info);
										},
										error => {
											// console.log('mail error', error);//log this
										}
									);
									delete userData.id;
									delete userData.password;
									delete userData.hitLimit;
									delete userData.hitPeriod;
									delete userData.hitCount;
									delete userData.activationKey;
									send(res, conn, userData);
								}, fail(res, conn, consts.e.ERR_DB_ERROR));
						}, fail(res, conn, consts.e.ERR_DB_ERROR));
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		};

		addUser();
	});
});

/* request user activation mail */
router.get('/me/reset-password/:email', authenticate, access('user:reset-pass-me', 'کاربر ایمیل تغییر پسورد درخواست کند'), function(req, res, next) {
	if(!/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.exec(req.params.email))
		return fail(res, null, consts.e.ERR_MALFORMED_REQUEST);

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, consts.e.ERR_DB_ERROR)();

		var user = new UserModel(conn, {email: req.params.email});
		user.load().then(
			user => {
				if(!user)
					return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

				user.generateActivationKey();
				user.save().then(
					() => {
						mailHelper.sendResetPasswordEMail(user).then(
							info => {
								// console.log(info);
							},
							error => {
								// console.log('mail error', error);//log this
							}
						);
						send(res, conn, {});
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* reset user's password */
router.post('/me/reset-password/:key', authenticate, access('user:reset-pass-me', 'کاربر ایمیل تغییر پسورد درخواست کند'), function(req, res, next) {
	if(!req.body.password)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, consts.e.ERR_DB_ERROR)();

		var user = new UserModel(conn, {activationKey: req.params.key});
		user.load().then(
			user => {
				if(!user) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

				if(user.field('state') != consts.v.STATE_ACTIVE)
 					return fail(res, conn, consts.e.ERR_ACCESS_PERMISSION)();
 				
 				user.field('password', md5(req.body.password));
				user.generateActivationKey(); // just to change the previously used key
				user.save().then(
					function(){
						var userData = user.fields();
						delete userData.id;
						delete userData.password;
						delete userData.hitLimit;
						delete userData.hitPeriod;
						delete userData.hitCount;
						delete userData.activationKey;
						send(res, conn, userData);
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* user updates his/her own data */
router.post('/me', authenticate, access('user:post', 'کاربر اطلاعات خودش را ویرایش کند'), function(req, res, next) {
	if(!req.user) fail(res, null, consts.e.ERR_AUTH)();
	var user = req.user;

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		var displaynameFlag = (req.body.hasOwnProperty('displayname') && req.body.displayname) || (user.field('displayname') != user.field('firstname') + ' ' + user.field('lastname'));

		if(req.body.hasOwnProperty('gender')) user.field('gender', parseInt(req.body.gender));
		if(req.body.hasOwnProperty('password')) user.field('password', md5(req.body.password));
		if(req.body.hasOwnProperty('avatar')) user.field('avatar', req.body.avatar);
		if(req.body.hasOwnProperty('displayname')) user.field('displayname', req.body.displayname);
		if(req.body.hasOwnProperty('firstname')) user.field('firstname', req.body.firstname);
		if(req.body.hasOwnProperty('lastname')) user.field('lastname', req.body.lastname);

		/* hitCount and timestamp fields are not editable */

		var manageAvatar = function(callback){
			var finishup = function(){
				user.field('avatar', req.body.avatar);
				callback();
			};

			var deletePreviousAvatar = function() {
				if(user.field('avatar'))
					glob(path.join(BASEDIR, 'private/avatar/'+user.field('avatar')+'*'), {}, function(err, files){
						if(err) return finishup();
						files.forEach(function(file){
							fs.unlinkSync(file);
						});
						finishup();
					});
				else finishup();
			};

			if(req.body.hasOwnProperty('avatar') && req.body.avatar!=user.field('avatar')) {
				if(req.body.avatar)
					fs.rename(path.join(BASEDIR, 'private/temp/'+req.body.avatar), path.join(BASEDIR, 'private/avatar/'+req.body.avatar), deletePreviousAvatar);
				else
					deletePreviousAvatar();
			}else callback();
		};

		if(displaynameFlag) {
			user.field('searchable', simplifyString(user.field('displayname')));
			user.field('sortable', simplifyString(user.field('displayname')));
		}else {
			user.field('displayname', user.field('firstname') + ' ' + user.field('lastname'))
			user.field('searchable', simplifyString(user.field('displayname')));
			user.field('sortable', simplifyString(user.field('lastname') + ' ' + user.field('firstname')));
		}

		manageAvatar(function(){
			user.save().then(
				function(){
					var userData = user.fields();
					delete userData.id;
					if(userData.userType == consts.v.USER_TYPE_PEOPLE)
						delete userData.key;
					delete userData.searchable;
					delete userData.sortable;
					delete userData.password;
					send(res, conn, userData);
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		});
	});
});

/* update an existing user */
router.post('/:email', authenticate, access('user:post', 'ویرایش اطلاعات کاربر'), function(req, res, next) {
	if(!req.params.email)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		(new UserModel(conn, {email: req.params.email.toLowerCase()})).load().then(
			function(user) {
				if(!user) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

				var prevrole = user.field('role');

				var displaynameFlag = (req.body.hasOwnProperty('displayname') && req.body.displayname) || (user.field('displayname') != user.field('firstname') + ' ' + user.field('lastname'));

				if(req.body.hasOwnProperty('key')) user.field('key', parseInt(req.body.key));
				if(req.body.hasOwnProperty('userType')) user.field('userType', parseInt(req.body.userType));
				if(req.body.hasOwnProperty('role')) user.field('role', parseInt(req.body.role));
				if(req.body.hasOwnProperty('gender')) user.field('gender', parseInt(req.body.gender));
				if(req.body.hasOwnProperty('password')) user.field('password', md5(req.body.password));
				if(req.body.hasOwnProperty('hitCount')) user.field('hitCount', parseInt(req.body.hitCount)); // e.g. to reset the counter.
				if(req.body.hasOwnProperty('hitLimit')) user.field('hitLimit', parseInt(req.body.hitLimit));
				if(req.body.hasOwnProperty('hitPeriod')) user.field('hitPeriod', parseInt(req.body.hitPeriod));
				if(req.body.hasOwnProperty('state')) user.field('state', parseInt(req.body.state));
				if(req.body.hasOwnProperty('displayname')) user.field('displayname', req.body.displayname);
				if(req.body.hasOwnProperty('firstname')) user.field('firstname', req.body.firstname);
				if(req.body.hasOwnProperty('lastname')) user.field('lastname', req.body.lastname);
				/* hitCount and timestamp fields are not editable */

				if(user.field('role') && user.field('role')==consts.v.ROLE_UNAUTHENTICATED) return fail(res, conn, consts.e.ERR_DATA_CONFLICT)();

				var manageAvatar = function(callback){
					var finishup = function(){
						user.field('avatar', req.body.avatar);
						callback();
					};

					var deletePreviousAvatar = function() {
						if(user.field('avatar'))
							glob(path.join(BASEDIR, 'private/avatar/'+user.field('avatar')+'*'), {}, function(err, files){
								if(err) return finishup();
								files.forEach(function(file){
									fs.unlinkSync(file);
								});
								finishup();
							});
						else finishup();
					};

					if(req.body.hasOwnProperty('avatar') && req.body.avatar!=user.field('avatar')) {
						if(req.body.avatar)
							fs.rename(path.join(BASEDIR, 'private/temp/'+req.body.avatar), path.join(BASEDIR, 'private/avatar/'+req.body.avatar), deletePreviousAvatar);
						else
							deletePreviousAvatar();
					}else callback();
				};

				if(displaynameFlag) {
					user.field('searchable', simplifyString(user.field('displayname')));
					user.field('sortable', simplifyString(user.field('displayname')));
				}else {
					user.field('displayname', user.field('firstname') + ' ' + user.field('lastname'))
					user.field('searchable', simplifyString(user.field('displayname')));
					user.field('sortable', simplifyString(user.field('lastname') + ' ' + user.field('firstname')));
				}

				manageAvatar(function(){
					user.save().then(
						function(){
							var userData = user.fields();
							delete userData.id;
							userData.hitData = user.hitData;

							if(prevrole!=user.field('role'))
								(new UserRoleModel(conn, {id: user.field('role')})).save().then(
									function(){
										(new UserRoleModel(conn, {id: prevrole})).save().then(
											function(){
												send(res, conn, userData);
											}, fail(res, conn, consts.e.ERR_DB_ERROR));
									}, fail(res, conn, consts.e.ERR_DB_ERROR));
							else
								send(res, conn, userData);
						}, fail(res, conn, consts.e.ERR_DB_ERROR));
				});

			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* delete an user */
router.delete('/:email', authenticate, access('user:delete', 'حذف کاربر'), function(req, res, next) {
	if(!req.params.email)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

	db.getConnection(function(err, conn){
		if(err) return fail(res, conn, err)();

		(new UserModel(conn, {email: req.params.email})).load().then(
			function(user) {
				if(!user) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
				user.delete().then(
					function(user){
						(new UserRoleModel(conn, {id: user.field('role')})).save().then(
							function(){
								send(res, conn, {email: user.field('email')});
							}, fail(res, conn, consts.e.ERR_DB_ERROR));
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

module.exports = router;