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/purchase.js
/**
 * Express endpoints for /purchase address
 * Version: 0.1
 * Author: Babak Vandad
 *
 * Restful api for these addresses:
 * 		GET			/purchase/me
 * 		GET			/purchase/:id
 * 		GET			/purchase/
 * 		PUT			/purchase/me
 * 		PUT			/purchase/
 * 		GET			/purchase/:id/confirm
 * 		POST		/purchase/:id
 * 		DELETE		/purchase/:id
 */

var express = require('express');
var router = express.Router();
var path = require('path');
var consts = require(path.join(BASEDIR, 'consts'));
var authHelper = require(path.join(BASEDIR, 'helpers/auth'));
var UserModel = require(path.join(BASEDIR, 'models/user'));
var UserAccessModel = require(path.join(BASEDIR, 'models/userAccess'));
var DictionaryModel = require(path.join(BASEDIR, 'models/dictionary'));
var PurchaseModel = require(path.join(BASEDIR, 'models/purchase'));
var SubscriptionModel = require(path.join(BASEDIR, 'models/subscription'));
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(){
		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 purchases of the current user */
router.get('/me', authenticate, access('purchase:get-me', 'کاربر سابقه خریدهای خودش را مشاهده کند'), function(req, res, next){
	if(!req.user || !req.isAuthenticated()) return fail(res, null, consts.e.ERR_AUTH)();

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

		var query = {};

		query.user = req.user.field('email');
		if(req.query.hasOwnProperty('book')) query.book = parseInt(req.query.book);
		if(req.query.hasOwnProperty('minValue')) query['>=value'] = parseInt(req.query.minValue);
		if(req.query.hasOwnProperty('maxValue')) query['<=value'] = parseInt(req.query.maxValue);
		if(req.query.hasOwnProperty('value')) query.value = req.query.value;
		if(req.query.hasOwnProperty('minDate')) query['>=dateExpire'] = req.query.minDate;
		if(req.query.hasOwnProperty('date')) {
			query['>=dateExpire'] = req.query.date;
			query['<=dateStart'] = req.query.date;
		}
		if(req.query.hasOwnProperty('maxDate')) query['<=dateStart'] = req.query.maxDate;

		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) {
			var response = [];
			return PurchaseModel.count(conn, query).then(
				function(sum) {
					records.map(function(record){
						delete record.user_searchable;
						delete record.user_sortable;
					});
					send(res, conn, {sum: sum, list: records});
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		};

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

/* load purchase by id */
router.get('/:id', authenticate, access('purchase:get-id', 'نمایش سابقه خریدهای کاربر'), function(req, res, next){
	if(!req.params.id) return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)('id is required.');

	reqid = parseInt(req.params.id);
	if(isNaN(reqid)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)('requested id is not a number.');

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

		var purchaseModel = new PurchaseModel(conn, {id: reqid});
		purchaseModel.load().then(
			function(purchase){
				if(!purchase) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
				send(res, conn, purchase.fields());
			},
			fail(res, conn, consts.e.ERR_DB_ERROR)
		);
	});
});

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

		var query = {};

		if(req.query.hasOwnProperty('user')) query.user = req.query.user;
		if(req.query.hasOwnProperty('book')) query.book = req.query.book;
		if(req.query.hasOwnProperty('state')) query.state = parseInt(req.query.state);
		if(req.query.hasOwnProperty('minValue')) query['>=value'] = parseInt(req.query.minValue);
		if(req.query.hasOwnProperty('maxValue')) query['<=value'] = parseInt(req.query.maxValue);
		if(req.query.hasOwnProperty('value')) query.value = req.query.value;
		if(req.query.hasOwnProperty('minDate')) query['>=dateExpire'] = req.query.minDate;
		if(req.query.hasOwnProperty('date')) {
			query['>=dateExpire'] = req.query.date;
			query['<=dateStart'] = req.query.date;
		}
		if(req.query.hasOwnProperty('maxDate')) query['<=dateStart'] = req.query.maxDate;

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

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

		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) {
			var response = [];
			return PurchaseModel.count(conn, query).then(
				function(sum) {
					send(res, conn, {sum: sum, list: records});
				}, fail(res, conn, consts.e.ERR_DB_ERROR));
		};

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

/* add a new purchase record for the current user */
router.put('/me', authenticate, access('purchase:put-me', 'کاربر خریدی برای خود ثبت کند'), function(req, res, next) {
	if(!req.user || !req.isAuthenticated()) return fail(res, null, consts.e.ERR_AUTH)();
	if(!req.body.book || !req.body.dateStart || !req.body.dateExpire)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

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

		(new DictionaryModel(conn, {uuid: req.body.book})).load().then(
			function(book) {
				if(!book) return fail(res, conn, consts.ERR_MISSING_RECORD)();

				var data = {
					user: req.user.field('id'),
					book: book.field('id'),
					dateStart: req.body.dateStart,
					dateExpire: req.body.dateExpire,
					state: consts.v.STATE_INACTIVE
				}

				// TODO: value must be read from the list price.
				if(req.body.value)
					data.value = parseInt(req.body.value);

				var purchaseModel = new PurchaseModel(conn, data);
				purchaseModel.save().then(
					function(purchase) {
						purchase.load().then(
							function() {
								send(res, conn, purchase.fields());
							}, fail(res, conn, consts.e.ERR_DB_ERROR));
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* add a new purchase record */
router.put('/', authenticate, access('purchase:put', 'افزودن سابقه خرید'), function(req, res, next) {
	if(!req.body.user || !req.body.book || !req.body.dateStart || !req.body.dateExpire)
		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.body.user})).load().then(
			function(user) {
				if(!user) return fail(res, conn, consts.ERR_MISSING_RECORD)();

				(new DictionaryModel(conn, {uuid: req.body.book})).load().then(
					function(book) {
						if(!book) return fail(res, conn, consts.ERR_MISSING_RECORD)();

						var data = {
							user: user.field('id'),
							book: book.field('id'),
							dateStart: req.body.dateStart,
							dateExpire: req.body.dateExpire,
							state: consts.v.STATE_INACTIVE
						};

						if(req.body.value)
							data.value = parseInt(req.body.value);

						(new PurchaseModel(conn, data)).save().then(
							function(purchase) {
								purchase.load().then(
									function() {
										send(res, conn, purchase.fields());
									}, fail(res, conn, consts.e.ERR_DB_ERROR));
							}, fail(res, conn, consts.e.ERR_DB_ERROR));
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* confirm a purchase and add a corresponding subscripton */
router.get('/:id/confirm', authenticate, access('purchase:get-id-confirm', 'تأیید خرید'), function(req, res, next){
	if(!req.params.id) return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)('id is required.');

	reqid = parseInt(req.params.id);
	if(isNaN(reqid)) return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)('requested id is not a number.');

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

		SubscriptionModel.list(conn, {record: reqid}).then(
			function(subscriptions){
				if(subscriptions.length) return fail(res, conn, consts.e.ERR_DATA_CONFLICT)();

				(new PurchaseModel(conn, {id: reqid})).load(true).then(
					function(purchase){
						if(!purchase) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

						purchase.field('state', consts.v.STATE_ACTIVE);
						purchase.save().then(
							function(){
								var subscriptionData = {
									user: purchase.field('user'),
									book: purchase.field('book'),
									record: purchase.field('id'),
									dateStart: purchase.field('dateStart'),
									dateExpire: purchase.field('dateExpire'),
									state: consts.v.STATE_ACTIVE
								};

								(new SubscriptionModel(conn, subscriptionData)).save().then(
									function(subscripton){
										purchase.load().then(
											function(){
												send(res, conn, purchase.fields());
											}, fail(res, conn, consts.e.ERR_DB_ERROR));
									}, fail(res, conn, consts.e.ERR_DB_ERROR));
							}, fail(res, conn, consts.e.ERR_DB_ERROR));
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR));
	});
});

/* update an existing purchase record */
router.post('/:id', authenticate, access('purchase:post-id', 'ویرایش اطلاعات خرید'), function(req, res, next) {
	if(!req.params.id)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

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

		var purchaseModel = new PurchaseModel(conn, {id: parseInt(req.params.id)});
		purchaseModel.load(true).then(
			function(purchase){
				if(!purchase) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

				// ignore user and book and amount from request. only dates and state may be updated
				if(req.body.hasOwnProperty('dateStart')) purchase.field('dateStart', req.body.dateStart);
				if(req.body.hasOwnProperty('dateExpire')) purchase.field('dateExpire', req.body.dateExpire);
				if(req.body.hasOwnProperty('state')) purchase.field('state', parseInt(req.body.state));

				purchase.save().then(
					function(purchase){
						req.body.id = parseInt(req.params.id);
						send(res, conn, req.body);
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR)
		);
	});
});

/* delete a purchase record */
router.delete('/:id', authenticate, access('purchase:delete-id', 'حذف خرید'), function(req, res, next) {
	if(!req.params.id)
		return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();

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

		var purchaseModel = new PurchaseModel(conn, {id: parseInt(req.params.id)});
		purchaseModel.load(true).then(
			function(purchase){
				if(!purchase) return fail(res, conn, consts.e.ERR_MISSING_RECORD)();

				purchase.delete().then(
					function(){
						send(res, conn, {id: req.params.id});
					}, fail(res, conn, consts.e.ERR_DB_ERROR));
			}, fail(res, conn, consts.e.ERR_DB_ERROR)
		);
	});
});

module.exports = router;