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;