File: //var/dev/farhangmoaser/web/routes/api/1.0/dictionary.js
/**
* Express endpoints for /dictionary address
* Version: 0.1
* Author: Babak Vandad
*
* Restful api for addresses:
* GET /dictionary/cat
* PUT /dictionary/cat
* POST /dictionary/cat
* DELETE /dictionary/cat/:id
* GET /dictionary/
* GET /dictionary/:uuid
* PUT /dictionary/
* POST /dictionary/:uuid
* DELETE /dictionary/:uuid
*/
var express = require('express');
var router = express.Router();
var fs = require('fs');
var glob = require('glob');
var path = require('path');
var consts = require(path.join(BASEDIR, 'consts'));
var authHelper = require(path.join(BASEDIR, 'helpers/auth'));
var UserAccessModel = require(path.join(BASEDIR, 'models/userAccess'));
var LongTextModel = require(path.join(BASEDIR, 'models/longText'));
var DictionaryCatModel = require(path.join(BASEDIR, 'models/dictionaryCat'));
var DictionaryModel = require(path.join(BASEDIR, 'models/dictionary'));
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(conn, res, data){
conn.release();
if(data)
res.json(data);
else
res.end();
};
/* Fetches list of dictionary categories. */
router.get('/cat/', authenticate, access('dictionary:cat:get', 'نمایش دستهبندی فرهنگها'), function(req, res, next) {
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
DictionaryCatModel.list(conn).then(
function (records) {
send(conn, res, records);
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
});
});
/**
* Adds a new dictionary category
* Category title is required
*/
router.put('/cat/', authenticate, access('dictionary:cat:put', 'افزودن دسته جدید'), function(req, res, next) {
if(!req.body.title)
return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
var dictionaryCatModel = new DictionaryCatModel(conn, {title: req.body.title});
dictionaryCatModel.load().then(
function(record){
if(record) return fail(res, conn, consts.e.ERR_MALFORMED_REQUEST)();
dictionaryCatModel.save().then(
function(record) {
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_DB_ERROR));
}, fail(res, conn, consts.e.ERR_DB_ERROR));
});
});
/**
* Updates an existing dictionary category.
* Category id or title is required.
*/
router.post('/cat/', authenticate, access('dictionary:cat:post', 'ویرایش نام دسته'), function(req, res, next) {
if(!req.body.title || !req.body.id)
return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
var dictionaryCatModel = new DictionaryCatModel(conn, {id: req.body.id, title: req.body.title});
dictionaryCatModel.save().then(
function(record) {
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
});
});
/**
* Deletes a dictionary category.
* Category id is required.
*/
router.delete('/cat/:id', authenticate, access('dictionary:cat: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 dictionaryCatModel = new DictionaryCatModel(conn, {id: req.params.id});
dictionaryCatModel.remove().then(
function(record) {
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
});
});
/* Fetches list of books. with minimum required info. */
router.get('/', authenticate, access('dictionary:get', 'نمایش فهرست فرهنگها'), function(req, res, next) {
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
DictionaryModel.list(conn).then(
function(records){
send(conn, res, records);//TODO: make it a standard response: {sum: records.length, list: records}
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
});
});
/* Fetches list of books. with minimum required info. */
router.get('/active', authenticate, access('dictionary:get-active', 'نمایش فهرست فرهنگهای فعال'), (req, res, next) => {
db.getConnection((err, conn) => {
if(err) return fail(res, conn, err)();
DictionaryModel.listActives(conn).then(
records => {
send(conn, res, {sum: records.length, list: records});
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
});
});
/**
* Fetches complete record of a book.
* A uuid with format of .{8}(-.{4}){3}-.{12} is required.
*/
router.get('/:uuid', authenticate, access('dictionary:get-uuid', 'نمایش جزئیات فرهنگ'), function(req, res, next) {
if(!/.{8}(-.{4}){3}-.{12}/.exec(req.params.uuid))
return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
/* Loads description and related categories of the book. */
var loadDetails = function(record){
record.getLangs().then(
function(langs) {
record.field('langs', langs.map(function(row){
return {from: row.lang_from, to: row.lang_to};
}));
record.getRelatedCategories().then(
function(categories) {
record.field('id', undefined);
record.field('categories', categories);
if(record.field('desc')) {
var longTextModel = new LongTextModel(conn, {id: record.field('desc')});
longTextModel.load().then(
function(longtext) {
record.field('desc', longtext.field('content'));
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
}else
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
}, fail(res, conn, consts.e.ERR_DB_ERROR));
};
var dictionaryModel = new DictionaryModel(conn, {uuid: req.params.uuid});
dictionaryModel.load().then(
function(record) {
if(!record)
return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
else{
if(req.query.recountEntries)
record.updateEntryCount().then(
function(){
loadDetails(record);
}, fail(res, conn, consts.e.ERR_DB_ERROR));
else
loadDetails(record);
}
}, fail(res, conn, consts.e.ERR_MISSING_RECORD)
);
});
});
/**
* Adds a new book.
* At least title, author and pubYear fields must be set.
*/
router.put('/', authenticate, access('dictionary:put', 'افزودن فرهنگ'), function(req, res, next) {
if(!req.body.title || !req.body.author || !req.body.pubYear)
return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
var saveBook = function(book) {
/* save the book record (source table only) */
book.save().then(
function(record) {
/* update source_lang table */
record.addLangs(req.body.langs).then(
function(langs){
/* update dictionary_cat_rel table */
record.relateCategories(req.body.categories).then(
function(categories) {
if(req.body.cover) {
/* move the cover image to the cover folder. */
fs.rename(path.join(BASEDIR, 'private/temp/'+req.body.cover), path.join(BASEDIR, 'private/cover/'+req.body.cover), function(err){
if(err)
return fail(res, conn, consts.e.ERR_FILE_ERROR)();
record.field('desc', req.body.desc);
record.field('id', undefined);
send(conn, res, record.fields());
});
}else
send(conn, res, record.fields());
}, fail(res, conn, consts.e.ERR_DB_ERROR, 200));
}, fail(res, conn, consts.e.ERR_DB_ERROR, 200));
}, fail(res, conn, consts.e.ERR_DB_ERROR));
}
/* First save the description */
var dictionaryModel = new DictionaryModel(conn, req.body);
if(req.body.desc) {
var longTextModel = new LongTextModel(conn, {content: req.body.desc});
longTextModel.save().then(
function(record){
dictionaryModel.field('desc', record.field('id'));
saveBook(dictionaryModel);
}, fail(res, conn, consts.e.ERR_DB_ERROR)
);
}else
saveBook(dictionaryModel);
});
});
/**
* Updates an existing book.
* A uuid with format of .{8}(-.{4}){3}-.{12} is required.
*/
router.post('/:uuid', authenticate, access('dictionary:post-uuid', 'ویرایش فرهنگ'), function(req, res, next) {
if(!/.{8}(-.{4}){3}-.{12}/.exec(req.params.uuid))
return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
if(!req.body.title || !req.body.author || !req.body.pubYear)
return fail(res, null, consts.e.ERR_REQUIRED_FIELDS)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
/* load the book */
var dictionaryModel = new DictionaryModel(conn, {uuid: req.params.uuid});
dictionaryModel.load().then(
function(record) {
if(!record)
return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
/* load langs of the book */
record.getLangs().then(
function(langs) {
/* load categories of the book */
record.getRelatedCategories().then(
function(categories) {
var longTextModel = new LongTextModel(conn, {id: record.field('desc')});
/* load the book description */
longTextModel.load().then(
function(description) {
/* update cover if needed */
var handleCover = function(callback, _fail){
if(req.body.cover == record.field('cover'))
return callback(req.body.cover);
glob(path.join(BASEDIR, 'private/cover/'+record.field('cover')+'*'), {}, function(err, files){
if(record.field('cover')) // do not delete every thing!
files.forEach(function(file){
fs.unlinkSync(file);
});
fs.rename(path.join(BASEDIR, 'private/temp/'+req.body.cover), path.join(BASEDIR, 'private/cover/'+req.body.cover), function(err){
if(err) return _fail();
callback(req.body.cover);
});
});
};
/* update categories if needed */
var handleCategories = function(callback, _fail){
oldcats = [];
categories.forEach(function(cat){
oldcats.push(cat.id);
});
if(req.body.categories.sort().join('-') == oldcats.sort().join('-'))
return callback();
record.relateCategories(req.body.categories).then(callback, _fail);
};
/* update languges if needed */
var handleLangs = function(callback, _fail){
/* make comparable strings */
var oldlangs = langs.map(function(row){
return row.from+'->'+row.to;
}).sort().join('|');
var newlangs = req.body.langs.map(function(row){
return row.from+'->'+row.to;
}).sort().join('|');
if(newlangs == oldlangs) return callback();
record.addLangs(req.body.langs).then(callback, _fail);
};
/* update book description if needed */
var handleDescription = function(callback, _fail){
if(longTextModel.field('content') == req.body.desc)
return callback();
longTextModel.field('content', req.body.desc);
if(!req.body.desc)
longTextModel.remove().then(callback, _fail);
else
longTextModel.save().then(function(textRecord){
if(record.field('desc') != textRecord.field('id')){
record.field('desc', textRecord.field('id'));
record.save().then(callback, _fail);
}else
callback();
}, _fail);
};
/* update chain */
handleCover(
function(coverid){
dictionaryModel.field('title', req.body.title);
dictionaryModel.field('author', req.body.author);
dictionaryModel.field('pubYear', req.body.pubYear);
dictionaryModel.field('cover', coverid);
dictionaryModel.field('state', req.body.state);
if(!description)
dictionaryModel.field('desc', null);
dictionaryModel.save().then(
function(){
handleLangs(
function(){
handleCategories(
function(){
handleDescription(
function(){
send(conn, res, null);
}, fail(res, conn, consts.e.ERR_DB_ERROR, 200));
}, fail(res, conn, consts.e.ERR_DB_ERROR, 200));
}, fail(res, conn, consts.e.ERR_DB_ERROR, 200));
}, fail(res, conn, consts.e.ERR_DB_ERROR));
}, fail(res, conn, consts.e.ERR_FILE_ERROR));
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
});
});
/**
* Deletes a book.
* A uuid with format of .{8}(-.{4}){3}-.{12} is required.
*/
router.delete('/:uuid', authenticate, access('dictionary:delete-uuid', 'حذف فرهنگ'), function(req, res, next) {
if(!/.{8}(-.{4}){3}-.{12}/.exec(req.params.uuid))
return fail(res, null, consts.e.ERR_MALFORMED_REQUEST)();
db.getConnection(function(err, conn){
if(err) return fail(res, conn, err)();
/* load the book (check if it exists and get the id instead of uuid) */
var dictionaryModel = new DictionaryModel(conn, {uuid: req.params.uuid});
dictionaryModel.load().then(
function(record) {
if(!record)
return fail(res, conn, consts.e.ERR_MISSING_RECORD)();
else
/* remove the book */
record.remove().then(
function(record) {
if(record.field('cover')) {
glob(path.join(BASEDIR, 'private/cover/'+record.field('cover')+'*'), {}, function(err, files){
files.forEach(function(file){
fs.unlinkSync(file);
});
send(conn, res, null);
});
}else
send(conn, res, null);
}, fail(res, conn, consts.e.ERR_DB_ERROR));
}, fail(res, conn, consts.e.ERR_MISSING_RECORD));
});
});
module.exports = router;