2015-08-18 21:27:14 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2016-02-17 05:11:55 +00:00
|
|
|
let msgDb = require('./database.js').dbs.message;
|
|
|
|
let Config = require('./config.js').config;
|
|
|
|
let Message = require('./message.js');
|
|
|
|
let Log = require('./logger.js').log;
|
|
|
|
let checkAcs = require('./acs_util.js').checkAcs;
|
|
|
|
let msgNetRecord = require('./msg_network.js').recordMessage;
|
2015-08-18 21:27:14 +00:00
|
|
|
|
2016-02-17 05:11:55 +00:00
|
|
|
let async = require('async');
|
|
|
|
let _ = require('lodash');
|
|
|
|
let assert = require('assert');
|
2015-08-18 21:27:14 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
exports.getAvailableMessageConferences = getAvailableMessageConferences;
|
|
|
|
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
|
|
|
exports.getAvailableMessageAreasByConfTag = getAvailableMessageAreasByConfTag;
|
|
|
|
exports.getSortedAvailMessageAreasByConfTag = getSortedAvailMessageAreasByConfTag;
|
|
|
|
exports.getDefaultMessageConferenceTag = getDefaultMessageConferenceTag;
|
|
|
|
exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
|
|
|
|
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
|
|
|
exports.getMessageAreaByTag = getMessageAreaByTag;
|
|
|
|
exports.changeMessageConference = changeMessageConference;
|
2015-08-20 22:35:04 +00:00
|
|
|
exports.changeMessageArea = changeMessageArea;
|
2015-08-28 04:20:24 +00:00
|
|
|
exports.getMessageListForArea = getMessageListForArea;
|
2015-12-31 05:29:10 +00:00
|
|
|
exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser;
|
2015-10-22 21:44:44 +00:00
|
|
|
exports.getMessageAreaLastReadId = getMessageAreaLastReadId;
|
|
|
|
exports.updateMessageAreaLastReadId = updateMessageAreaLastReadId;
|
2016-02-21 00:57:38 +00:00
|
|
|
exports.persistMessage = persistMessage;
|
2016-06-20 03:09:45 +00:00
|
|
|
exports.trimMessageAreasScheduledEvent = trimMessageAreasScheduledEvent;
|
2015-08-18 21:27:14 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
const CONF_AREA_RW_ACS_DEFAULT = 'GM[users]';
|
|
|
|
const AREA_MANAGE_ACS_DEFAULT = 'GM[sysops]';
|
|
|
|
|
|
|
|
const AREA_ACS_DEFAULT = {
|
2016-04-13 04:38:32 +00:00
|
|
|
read : CONF_AREA_RW_ACS_DEFAULT,
|
|
|
|
write : CONF_AREA_RW_ACS_DEFAULT,
|
|
|
|
manage : AREA_MANAGE_ACS_DEFAULT,
|
2016-02-03 04:35:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
function getAvailableMessageConferences(client, options) {
|
2016-04-13 04:38:32 +00:00
|
|
|
options = options || { includeSystemInternal : false };
|
2016-02-03 04:35:59 +00:00
|
|
|
|
|
|
|
// perform ACS check per conf & omit system_internal if desired
|
2016-04-13 04:38:32 +00:00
|
|
|
return _.omit(Config.messageConferences, (v, k) => {
|
|
|
|
if(!options.includeSystemInternal && 'system_internal' === k) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const readAcs = v.acs || CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
return !checkAcs(client, readAcs);
|
|
|
|
});
|
2016-02-03 04:35:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getSortedAvailMessageConferences(client, options) {
|
2016-03-26 02:47:25 +00:00
|
|
|
const sorted = _.map(getAvailableMessageConferences(client, options), (v, k) => {
|
2016-02-03 04:35:59 +00:00
|
|
|
return {
|
|
|
|
confTag : k,
|
|
|
|
conf : v,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
sorted.sort((a, b) => {
|
2016-03-25 05:10:08 +00:00
|
|
|
const keyA = a.conf.sort ? a.conf.sort.toString() : a.conf.name;
|
|
|
|
const keyB = b.conf.sort ? b.conf.sort.toString() : b.conf.name;
|
|
|
|
return keyA.localeCompare(keyB);
|
2016-02-03 04:35:59 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return sorted;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return an *object* of available areas within |confTag|
|
|
|
|
function getAvailableMessageAreasByConfTag(confTag, options) {
|
2015-10-18 03:39:54 +00:00
|
|
|
options = options || {};
|
2016-03-23 04:24:00 +00:00
|
|
|
|
|
|
|
// :TODO: confTag === "" then find default
|
2015-10-18 03:39:54 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
if(_.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
2016-04-13 04:38:32 +00:00
|
|
|
const areas = Config.messageConferences[confTag].areas;
|
|
|
|
|
|
|
|
if(!options.client || true === options.noAcsCheck) {
|
|
|
|
// everything - no ACS checks
|
|
|
|
return areas;
|
|
|
|
} else {
|
|
|
|
// perform ACS check per area
|
|
|
|
return _.omit(areas, v => {
|
|
|
|
const readAcs = _.has(v, 'acs.read') ? v.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
return !checkAcs(options.client, readAcs);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2016-02-03 04:35:59 +00:00
|
|
|
}
|
2015-10-18 03:39:54 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getSortedAvailMessageAreasByConfTag(confTag, options) {
|
2016-03-25 05:10:08 +00:00
|
|
|
const areas = _.map(getAvailableMessageAreasByConfTag(confTag, options), (v, k) => {
|
|
|
|
return {
|
|
|
|
areaTag : k,
|
|
|
|
area : v,
|
2016-04-13 04:38:32 +00:00
|
|
|
};
|
2016-03-25 05:10:08 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
areas.sort((a, b) => {
|
|
|
|
const keyA = a.area.sort ? a.area.sort.toString() : a.area.name;
|
|
|
|
const keyB = b.area.sort ? b.area.sort.toString() : b.area.name;
|
|
|
|
return keyA.localeCompare(keyB);
|
|
|
|
});
|
|
|
|
|
|
|
|
return areas;
|
2015-08-20 22:35:04 +00:00
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
2015-10-18 02:03:51 +00:00
|
|
|
//
|
2016-02-03 04:35:59 +00:00
|
|
|
// Find the first conference marked 'default'. If found,
|
|
|
|
// inspect |client| against *read* ACS using defaults if not
|
|
|
|
// specified.
|
|
|
|
//
|
|
|
|
// If the above fails, just go down the list until we get one
|
|
|
|
// that passes.
|
2015-10-18 02:03:51 +00:00
|
|
|
//
|
2016-02-03 04:35:59 +00:00
|
|
|
// It's possible that we end up with nothing here!
|
|
|
|
//
|
|
|
|
// Note that built in 'system_internal' is always ommited here
|
|
|
|
//
|
2016-06-20 03:09:45 +00:00
|
|
|
let defaultConf = _.findKey(Config.messageConferences, o => o.default);
|
|
|
|
if(defaultConf) {
|
|
|
|
const acs = Config.messageConferences[defaultConf].acs || CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
if(true === disableAcsCheck || checkAcs(client, acs)) {
|
|
|
|
return defaultConf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// just use anything we can
|
|
|
|
defaultConf = _.findKey(Config.messageConferences, (o, k) => {
|
|
|
|
const acs = o.acs || CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
return 'system_internal' !== k && (true === disableAcsCheck || checkAcs(client, acs));
|
|
|
|
});
|
2016-02-03 04:35:59 +00:00
|
|
|
|
2016-06-20 03:09:45 +00:00
|
|
|
return defaultConf;
|
2015-09-26 06:20:17 +00:00
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getDefaultMessageAreaTagByConfTag(client, confTag, disableAcsCheck) {
|
2016-06-20 03:09:45 +00:00
|
|
|
//
|
|
|
|
// Similar to finding the default conference:
|
|
|
|
// Find the first entry marked 'default', if any. If found, check | client| against
|
|
|
|
// *read* ACS. If this fails, just find the first one we can that passes checks.
|
|
|
|
//
|
|
|
|
// It's possible that we end up with nothing!
|
|
|
|
//
|
|
|
|
confTag = confTag || getDefaultMessageConferenceTag(client);
|
|
|
|
|
|
|
|
if(confTag && _.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
|
|
|
const areaPool = Config.messageConferences[confTag].areas;
|
|
|
|
let defaultArea = _.findKey(areaPool, o => o.default);
|
|
|
|
if(defaultArea) {
|
|
|
|
const readAcs = _.has(areaPool, [ defaultArea, 'acs', 'read' ]) ? areaPool[defaultArea].acs.read : AREA_ACS_DEFAULT.read;
|
|
|
|
if(true === disableAcsCheck || checkAcs(client, readAcs)) {
|
|
|
|
return defaultArea;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultArea = _.findKey(areaPool, (o, k) => {
|
|
|
|
const readAcs = _.has(areaPool, [ defaultArea, 'acs', 'read' ]) ? areaPool[defaultArea].acs.read : AREA_ACS_DEFAULT.read;
|
|
|
|
return (true === disableAcsCheck || checkAcs(client, readAcs));
|
|
|
|
});
|
|
|
|
|
|
|
|
return defaultArea;
|
|
|
|
}
|
2016-02-03 04:35:59 +00:00
|
|
|
}
|
2015-08-21 04:51:00 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getMessageConferenceByTag(confTag) {
|
|
|
|
return Config.messageConferences[confTag];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getMessageAreaByTag(areaTag, optionalConfTag) {
|
2016-06-20 03:09:45 +00:00
|
|
|
const confs = Config.messageConferences;
|
|
|
|
|
|
|
|
if(_.isString(optionalConfTag)) {
|
|
|
|
if(_.has(confs, [ optionalConfTag, 'areas', areaTag ])) {
|
|
|
|
return confs[optionalConfTag].areas[areaTag];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
|
|
// No confTag to work with - we'll have to search through them all
|
|
|
|
//
|
|
|
|
var area;
|
|
|
|
_.forEach(confs, (v) => {
|
|
|
|
if(_.has(v, [ 'areas', areaTag ])) {
|
|
|
|
area = v.areas[areaTag];
|
|
|
|
return false; // stop iteration
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return area;
|
|
|
|
}
|
2016-02-03 04:35:59 +00:00
|
|
|
}
|
2015-08-21 04:51:00 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function changeMessageConference(client, confTag, cb) {
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getConf(callback) {
|
|
|
|
const conf = getMessageConferenceByTag(confTag);
|
|
|
|
|
|
|
|
if(conf) {
|
|
|
|
callback(null, conf);
|
|
|
|
} else {
|
|
|
|
callback(new Error('Invalid message conference tag'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function getDefaultAreaInConf(conf, callback) {
|
|
|
|
const areaTag = getDefaultMessageAreaTagByConfTag(client, confTag);
|
|
|
|
const area = getMessageAreaByTag(areaTag, confTag);
|
|
|
|
|
|
|
|
if(area) {
|
|
|
|
callback(null, conf, { areaTag : areaTag, area : area } );
|
|
|
|
} else {
|
|
|
|
callback(new Error('No available areas for this user in conference'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function validateAccess(conf, areaInfo, callback) {
|
|
|
|
const confAcs = conf.acs || CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
|
|
|
|
if(!checkAcs(client, confAcs)) {
|
|
|
|
callback(new Error('User does not have access to this conference'));
|
|
|
|
} else {
|
|
|
|
const areaAcs = _.has(areaInfo, 'area.acs.read') ? areaInfo.area.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
if(!checkAcs(client, areaAcs)) {
|
|
|
|
callback(new Error('User does not have access to default area in this conference'));
|
|
|
|
} else {
|
|
|
|
callback(null, conf, areaInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function changeConferenceAndArea(conf, areaInfo, callback) {
|
|
|
|
const newProps = {
|
|
|
|
message_conf_tag : confTag,
|
|
|
|
message_area_tag : areaInfo.areaTag,
|
|
|
|
};
|
|
|
|
client.user.persistProperties(newProps, err => {
|
|
|
|
callback(err, conf, areaInfo);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
],
|
|
|
|
function complete(err, conf, areaInfo) {
|
|
|
|
if(!err) {
|
|
|
|
client.log.info( { confTag : confTag, confName : conf.name, areaTag : areaInfo.areaTag }, 'Current message conference changed');
|
|
|
|
} else {
|
|
|
|
client.log.warn( { confTag : confTag, error : err.message }, 'Could not change message conference');
|
|
|
|
}
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
);
|
2015-08-21 04:51:00 +00:00
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function changeMessageArea(client, areaTag, cb) {
|
2015-08-20 22:35:04 +00:00
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getArea(callback) {
|
2016-02-03 04:35:59 +00:00
|
|
|
const area = getMessageAreaByTag(areaTag);
|
2015-08-27 05:25:49 +00:00
|
|
|
|
2015-08-21 04:51:00 +00:00
|
|
|
if(area) {
|
|
|
|
callback(null, area);
|
|
|
|
} else {
|
2016-02-03 04:35:59 +00:00
|
|
|
callback(new Error('Invalid message area tag'));
|
2015-08-21 04:51:00 +00:00
|
|
|
}
|
2015-08-20 22:35:04 +00:00
|
|
|
},
|
|
|
|
function validateAccess(area, callback) {
|
2016-02-03 04:35:59 +00:00
|
|
|
//
|
|
|
|
// Need at least *read* to access the area
|
|
|
|
//
|
|
|
|
const readAcs = _.has(area, 'acs.read') ? area.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
|
|
|
if(!checkAcs(client, readAcs)) {
|
2015-11-05 06:04:55 +00:00
|
|
|
callback(new Error('User does not have access to this area'));
|
|
|
|
} else {
|
|
|
|
callback(null, area);
|
|
|
|
}
|
2015-08-20 22:35:04 +00:00
|
|
|
},
|
|
|
|
function changeArea(area, callback) {
|
2016-02-03 04:35:59 +00:00
|
|
|
client.user.persistProperty('message_area_tag', areaTag, function persisted(err) {
|
2015-08-20 22:35:04 +00:00
|
|
|
callback(err, area);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err, area) {
|
|
|
|
if(!err) {
|
2016-02-03 04:35:59 +00:00
|
|
|
client.log.info( { areaTag : areaTag, area : area }, 'Current message area changed');
|
2015-08-20 22:35:04 +00:00
|
|
|
} else {
|
2016-02-03 04:35:59 +00:00
|
|
|
client.log.warn( { areaTag : areaTag, area : area, error : err.message }, 'Could not change message area');
|
2015-08-20 22:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2015-08-27 05:25:49 +00:00
|
|
|
|
2016-01-04 00:47:39 +00:00
|
|
|
function getMessageFromRow(row) {
|
|
|
|
return {
|
|
|
|
messageId : row.message_id,
|
|
|
|
messageUuid : row.message_uuid,
|
|
|
|
replyToMsgId : row.reply_to_message_id,
|
|
|
|
toUserName : row.to_user_name,
|
|
|
|
fromUserName : row.from_user_name,
|
|
|
|
subject : row.subject,
|
|
|
|
modTimestamp : row.modified_timestamp,
|
|
|
|
viewCount : row.view_count,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
2015-12-31 05:29:10 +00:00
|
|
|
//
|
2016-02-03 04:35:59 +00:00
|
|
|
// If |areaTag| is Message.WellKnownAreaTags.Private,
|
2015-12-31 05:29:10 +00:00
|
|
|
// only messages addressed to |userId| should be returned.
|
|
|
|
//
|
|
|
|
// Only messages > lastMessageId should be returned
|
|
|
|
//
|
|
|
|
var msgList = [];
|
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getLastMessageId(callback) {
|
2016-02-03 04:35:59 +00:00
|
|
|
getMessageAreaLastReadId(userId, areaTag, function fetched(err, lastMessageId) {
|
2015-12-31 05:29:10 +00:00
|
|
|
callback(null, lastMessageId || 0); // note: willingly ignoring any errors here!
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function getMessages(lastMessageId, callback) {
|
2016-04-13 04:38:32 +00:00
|
|
|
let sql =
|
|
|
|
`SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count
|
|
|
|
FROM message
|
|
|
|
WHERE area_tag = "${areaTag}" AND message_id > ${lastMessageId}`;
|
2015-12-31 05:29:10 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
if(Message.WellKnownAreaTags.Private === areaTag) {
|
2015-12-31 05:29:10 +00:00
|
|
|
sql +=
|
2016-04-13 04:38:32 +00:00
|
|
|
` AND message_id in (
|
|
|
|
SELECT message_id from message_meta where meta_category ="System"
|
2016-04-15 04:11:01 +00:00
|
|
|
AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${userId})`;
|
2015-12-31 05:29:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sql += ' ORDER BY message_id;';
|
2016-01-04 00:47:39 +00:00
|
|
|
|
2015-12-31 05:29:10 +00:00
|
|
|
msgDb.each(sql, function msgRow(err, row) {
|
|
|
|
if(!err) {
|
2016-01-04 00:47:39 +00:00
|
|
|
msgList.push(getMessageFromRow(row));
|
2015-12-31 05:29:10 +00:00
|
|
|
}
|
|
|
|
}, callback);
|
|
|
|
}
|
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
cb(err, msgList);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getMessageListForArea(options, areaTag, cb) {
|
2015-08-27 05:25:49 +00:00
|
|
|
//
|
|
|
|
// options.client (required)
|
|
|
|
//
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
options.client.log.debug( { areaTag : areaTag }, 'Fetching available messages');
|
2015-09-04 22:17:41 +00:00
|
|
|
|
2015-08-27 05:25:49 +00:00
|
|
|
assert(_.isObject(options.client));
|
|
|
|
|
|
|
|
/*
|
|
|
|
[
|
|
|
|
{
|
|
|
|
messageId, messageUuid, replyToId, toUserName, fromUserName, subject, modTimestamp,
|
|
|
|
status(new|old),
|
|
|
|
viewCount
|
|
|
|
}
|
|
|
|
]
|
|
|
|
*/
|
|
|
|
|
|
|
|
var msgList = [];
|
|
|
|
|
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function fetchMessages(callback) {
|
|
|
|
msgDb.each(
|
2015-08-27 22:14:56 +00:00
|
|
|
'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' +
|
|
|
|
'FROM message ' +
|
2016-02-03 04:35:59 +00:00
|
|
|
'WHERE area_tag = ? ' +
|
2015-08-27 05:25:49 +00:00
|
|
|
'ORDER BY message_id;',
|
2016-02-03 04:35:59 +00:00
|
|
|
[ areaTag.toLowerCase() ],
|
2015-08-27 05:25:49 +00:00
|
|
|
function msgRow(err, row) {
|
|
|
|
if(!err) {
|
2016-01-04 00:47:39 +00:00
|
|
|
msgList.push(getMessageFromRow(row));
|
2015-08-27 05:25:49 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
2015-08-28 04:20:24 +00:00
|
|
|
},
|
|
|
|
function fetchStatus(callback) {
|
|
|
|
callback(null);// :TODO: fixmeh.
|
2015-08-27 05:25:49 +00:00
|
|
|
}
|
2015-08-28 04:20:24 +00:00
|
|
|
],
|
|
|
|
function complete(err) {
|
|
|
|
cb(err, msgList);
|
|
|
|
}
|
2015-08-27 05:25:49 +00:00
|
|
|
);
|
|
|
|
}
|
2015-09-06 21:58:58 +00:00
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function getMessageAreaLastReadId(userId, areaTag, cb) {
|
2015-10-22 21:44:44 +00:00
|
|
|
msgDb.get(
|
|
|
|
'SELECT message_id ' +
|
|
|
|
'FROM user_message_area_last_read ' +
|
2016-02-03 04:35:59 +00:00
|
|
|
'WHERE user_id = ? AND area_tag = ?;',
|
|
|
|
[ userId, areaTag ],
|
2016-01-04 00:47:39 +00:00
|
|
|
function complete(err, row) {
|
|
|
|
cb(err, row ? row.message_id : 0);
|
|
|
|
}
|
2015-10-22 21:44:44 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-02-03 04:35:59 +00:00
|
|
|
function updateMessageAreaLastReadId(userId, areaTag, messageId, cb) {
|
2015-10-22 21:44:44 +00:00
|
|
|
// :TODO: likely a better way to do this...
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getCurrent(callback) {
|
2016-02-03 04:35:59 +00:00
|
|
|
getMessageAreaLastReadId(userId, areaTag, function result(err, lastId) {
|
2015-10-22 21:44:44 +00:00
|
|
|
lastId = lastId || 0;
|
|
|
|
callback(null, lastId); // ignore errors as we default to 0
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function update(lastId, callback) {
|
|
|
|
if(messageId > lastId) {
|
|
|
|
msgDb.run(
|
2016-02-03 04:35:59 +00:00
|
|
|
'REPLACE INTO user_message_area_last_read (user_id, area_tag, message_id) ' +
|
2015-10-22 21:44:44 +00:00
|
|
|
'VALUES (?, ?, ?);',
|
2016-02-03 04:35:59 +00:00
|
|
|
[ userId, areaTag, messageId ],
|
|
|
|
function written(err) {
|
|
|
|
callback(err, true); // true=didUpdate
|
|
|
|
}
|
2015-10-22 21:44:44 +00:00
|
|
|
);
|
2016-01-04 00:47:39 +00:00
|
|
|
} else {
|
|
|
|
callback(null);
|
2015-10-22 21:44:44 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-04 00:47:39 +00:00
|
|
|
],
|
2016-02-03 04:35:59 +00:00
|
|
|
function complete(err, didUpdate) {
|
2016-01-04 00:47:39 +00:00
|
|
|
if(err) {
|
|
|
|
Log.debug(
|
2016-02-03 04:35:59 +00:00
|
|
|
{ error : err.toString(), userId : userId, areaTag : areaTag, messageId : messageId },
|
2016-01-04 00:47:39 +00:00
|
|
|
'Failed updating area last read ID');
|
|
|
|
} else {
|
2016-02-03 04:35:59 +00:00
|
|
|
if(true === didUpdate) {
|
|
|
|
Log.trace(
|
|
|
|
{ userId : userId, areaTag : areaTag, messageId : messageId },
|
|
|
|
'Area last read ID updated');
|
|
|
|
}
|
2016-01-04 00:47:39 +00:00
|
|
|
}
|
|
|
|
cb(err);
|
|
|
|
}
|
2015-10-22 21:44:44 +00:00
|
|
|
);
|
|
|
|
}
|
2016-02-17 05:11:55 +00:00
|
|
|
|
|
|
|
function persistMessage(message, cb) {
|
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function persistMessageToDisc(callback) {
|
|
|
|
message.persist(callback);
|
|
|
|
},
|
|
|
|
function recordToMessageNetworks(callback) {
|
|
|
|
msgNetRecord(message, callback);
|
|
|
|
}
|
|
|
|
],
|
|
|
|
cb
|
|
|
|
);
|
2016-06-20 03:09:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function trimMessagesToMax(areaTag, maxMessages, archivePath, cb) {
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getRemoteCount(callback) {
|
|
|
|
let removeCount = 0;
|
|
|
|
msgDb.get(
|
|
|
|
`SELECT COUNT(area_tag) AS msgCount
|
|
|
|
FROM message
|
|
|
|
WHERE area_tag = ?`,
|
|
|
|
[ areaTag ],
|
|
|
|
(err, row) => {
|
|
|
|
if(!err) {
|
|
|
|
if(row.msgCount >= maxMessages) {
|
|
|
|
removeCount = row.msgCount - maxMessages;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return callback(err, removeCount);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
function trimMessages(removeCount, callback) {
|
|
|
|
if(0 === removeCount) {
|
|
|
|
return callback(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(archivePath) {
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// just delete 'em
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// method exposed for event scheduler
|
|
|
|
function trimMessageAreasScheduledEvent(args, cb) {
|
|
|
|
//
|
|
|
|
// Available args:
|
|
|
|
// - archive:/path/to/archive/dir/
|
|
|
|
//
|
|
|
|
let archivePath;
|
|
|
|
if(args) {
|
|
|
|
args.forEach(a => {
|
|
|
|
if(a.startsWith('archive:')) {
|
|
|
|
archivePath = a.split(':')[1];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Find all area_tag's in message. We don't rely on user configurations
|
|
|
|
// in case one is no longer available. From there we can trim messages
|
|
|
|
// that meet the criteria (too old, too many, ...) and optionally archive
|
|
|
|
// them via moving them to a new DB with the same layout
|
|
|
|
//
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getAreaTags(callback) {
|
|
|
|
let areaTags = [];
|
|
|
|
msgDb.each(
|
|
|
|
`SELECT DISTINCT area_tag
|
|
|
|
FROM message;`,
|
|
|
|
(err, row) => {
|
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
areaTags.push(row.area_tag);
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
return callback(err, areaTags);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
function trimAreas(areaTags, callback) {
|
|
|
|
areaTags.forEach(areaTag => {
|
|
|
|
|
|
|
|
let maxMessages = Config.messageAreaDefaults.maxMessages;
|
|
|
|
let maxAgeDays = Config.messageAreaDefaults.maxAgeDays;
|
|
|
|
|
|
|
|
const area = getMessageAreaByTag(areaTag); // note: we don't know the conf
|
|
|
|
if(area) {
|
|
|
|
if(area.maxMessages) {
|
|
|
|
maxMessages = area.maxMessages;
|
|
|
|
}
|
|
|
|
if(area.maxAgeDays) {
|
|
|
|
maxAgeDays = area.maxAgeDays;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(maxMessages) {
|
|
|
|
trimMessagesToMax(areaTag, maxMessages, archivePath, err => {
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
console.log('trimming messages from scheduled event') // :TODO: remove me!!!
|
|
|
|
|
2016-02-17 05:11:55 +00:00
|
|
|
}
|