MAJOR refactor of Message class
* ES6 class vs old style * Add findMessages(filter, ...) similar to FileEntry.findFiles() allowing many filter types used throughout the system
This commit is contained in:
parent
ec1876084c
commit
70b5d7a124
432
core/message.js
432
core/message.js
|
@ -5,9 +5,11 @@ const msgDb = require('./database.js').dbs.message;
|
||||||
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||||
const ftnUtil = require('./ftn_util.js');
|
const ftnUtil = require('./ftn_util.js');
|
||||||
const createNamedUUID = require('./uuid_util.js').createNamedUUID;
|
const createNamedUUID = require('./uuid_util.js').createNamedUUID;
|
||||||
const getISOTimestampString = require('./database.js').getISOTimestampString;
|
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
const ANSI = require('./ansi_term.js');
|
const ANSI = require('./ansi_term.js');
|
||||||
|
const {
|
||||||
|
sanatizeString,
|
||||||
|
getISOTimestampString } = require('./database.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isAnsi, isFormattedLine,
|
isAnsi, isFormattedLine,
|
||||||
|
@ -25,74 +27,15 @@ const assert = require('assert');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const iconvEncode = require('iconv-lite').encode;
|
const iconvEncode = require('iconv-lite').encode;
|
||||||
|
|
||||||
module.exports = Message;
|
|
||||||
|
|
||||||
const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse('154506df-1df8-46b9-98f8-ebb5815baaf8');
|
const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse('154506df-1df8-46b9-98f8-ebb5815baaf8');
|
||||||
|
|
||||||
function Message(options) {
|
const WELL_KNOWN_AREA_TAGS = {
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this.messageId = options.messageId || 0; // always generated @ persist
|
|
||||||
this.areaTag = options.areaTag || Message.WellKnownAreaTags.Invalid;
|
|
||||||
|
|
||||||
if(options.uuid) {
|
|
||||||
// note: new messages have UUID generated @ time of persist. See also Message.createMessageUUID()
|
|
||||||
this.uuid = options.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replyToMsgId = options.replyToMsgId || 0;
|
|
||||||
this.toUserName = options.toUserName || '';
|
|
||||||
this.fromUserName = options.fromUserName || '';
|
|
||||||
this.subject = options.subject || '';
|
|
||||||
this.message = options.message || '';
|
|
||||||
|
|
||||||
if(_.isDate(options.modTimestamp) || moment.isMoment(options.modTimestamp)) {
|
|
||||||
this.modTimestamp = moment(options.modTimestamp);
|
|
||||||
} else if(_.isString(options.modTimestamp)) {
|
|
||||||
this.modTimestamp = moment(options.modTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.viewCount = options.viewCount || 0;
|
|
||||||
|
|
||||||
this.meta = {
|
|
||||||
System : {}, // we'll always have this one
|
|
||||||
};
|
|
||||||
|
|
||||||
if(_.isObject(options.meta)) {
|
|
||||||
_.defaultsDeep(this.meta, options.meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.meta) {
|
|
||||||
this.meta = options.meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hashTags = options.hashTags || [];
|
|
||||||
|
|
||||||
this.isValid = function() {
|
|
||||||
// :TODO: validate as much as possible
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isPrivate = function() {
|
|
||||||
return Message.isPrivateAreaTag(this.areaTag);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isFromRemoteUser = function() {
|
|
||||||
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Message.WellKnownAreaTags = {
|
|
||||||
Invalid : '',
|
Invalid : '',
|
||||||
Private : 'private_mail',
|
Private : 'private_mail',
|
||||||
Bulletin : 'local_bulletin',
|
Bulletin : 'local_bulletin',
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.isPrivateAreaTag = function(areaTag) {
|
const SYSTEM_META_NAMES = {
|
||||||
return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private;
|
|
||||||
};
|
|
||||||
|
|
||||||
Message.SystemMetaNames = {
|
|
||||||
LocalToUserID : 'local_to_user_id',
|
LocalToUserID : 'local_to_user_id',
|
||||||
LocalFromUserID : 'local_from_user_id',
|
LocalFromUserID : 'local_from_user_id',
|
||||||
StateFlags0 : 'state_flags0', // See Message.StateFlags0
|
StateFlags0 : 'state_flags0', // See Message.StateFlags0
|
||||||
|
@ -103,19 +46,20 @@ Message.SystemMetaNames = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Types for Message.SystemMetaNames.ExternalFlavor meta
|
// Types for Message.SystemMetaNames.ExternalFlavor meta
|
||||||
Message.AddressFlavor = {
|
const ADDRESS_FLAVOR = {
|
||||||
Local : 'local', // local / non-remote addressing
|
Local : 'local', // local / non-remote addressing
|
||||||
FTN : 'ftn', // FTN style
|
FTN : 'ftn', // FTN style
|
||||||
Email : 'email',
|
Email : 'email',
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.StateFlags0 = {
|
const STATE_FLAGS0 = {
|
||||||
None : 0x00000000,
|
None : 0x00000000,
|
||||||
Imported : 0x00000001, // imported from foreign system
|
Imported : 0x00000001, // imported from foreign system
|
||||||
Exported : 0x00000002, // exported to foreign system
|
Exported : 0x00000002, // exported to foreign system
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.FtnPropertyNames = {
|
// :TODO: these should really live elsewhere...
|
||||||
|
const FTN_PROPERTY_NAMES = {
|
||||||
// packet header oriented
|
// packet header oriented
|
||||||
FtnOrigNode : 'ftn_orig_node',
|
FtnOrigNode : 'ftn_orig_node',
|
||||||
FtnDestNode : 'ftn_dest_node',
|
FtnDestNode : 'ftn_dest_node',
|
||||||
|
@ -143,29 +87,91 @@ Message.FtnPropertyNames = {
|
||||||
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: kludges are stored with their names as-is
|
module.exports = class Message {
|
||||||
|
constructor(
|
||||||
|
{
|
||||||
|
messageId = 0, areaTag = Message.WellKnownAreaTags.Invalid, uuid, replyToMsgId = 0,
|
||||||
|
toUserName = '', fromUserName = '', subject = '', message = '', modTimestamp = moment(),
|
||||||
|
meta, hashTags = [],
|
||||||
|
} = { }
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.areaTag = areaTag;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.replyToMsgId = replyToMsgId;
|
||||||
|
this.toUserName = toUserName;
|
||||||
|
this.fromUserName = fromUserName;
|
||||||
|
this.subject = subject;
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
Message.prototype.setLocalToUserId = function(userId) {
|
if(_.isDate(modTimestamp) || _.isString(modTimestamp)) {
|
||||||
|
modTimestamp = moment(modTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modTimestamp = modTimestamp;
|
||||||
|
|
||||||
|
this.meta = {};
|
||||||
|
_.defaultsDeep(this.meta, { System : {} }, meta);
|
||||||
|
|
||||||
|
this.hashTags = hashTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
|
||||||
|
|
||||||
|
static isPrivateAreaTag(areaTag) {
|
||||||
|
return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPrivate() {
|
||||||
|
return Message.isPrivateAreaTag(this.areaTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFromRemoteUser() {
|
||||||
|
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get WellKnownAreaTags() {
|
||||||
|
return WELL_KNOWN_AREA_TAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get SystemMetaNames() {
|
||||||
|
return SYSTEM_META_NAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get AddressFlavor() {
|
||||||
|
return ADDRESS_FLAVOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get StateFlags0() {
|
||||||
|
return STATE_FLAGS0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get FtnPropertyNames() {
|
||||||
|
return FTN_PROPERTY_NAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalToUserId(userId) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.setLocalFromUserId = function(userId) {
|
setLocalFromUserId(userId) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.LocalFromUserID] = userId;
|
this.meta.System[Message.SystemMetaNames.LocalFromUserID] = userId;
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.setRemoteToUser = function(remoteTo) {
|
setRemoteToUser(remoteTo) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.RemoteToUser] = remoteTo;
|
this.meta.System[Message.SystemMetaNames.RemoteToUser] = remoteTo;
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.setExternalFlavor = function(flavor) {
|
setExternalFlavor(flavor) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.ExternalFlavor] = flavor;
|
this.meta.System[Message.SystemMetaNames.ExternalFlavor] = flavor;
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.createMessageUUID = function(areaTag, modTimestamp, subject, body) {
|
static createMessageUUID(areaTag, modTimestamp, subject, body) {
|
||||||
assert(_.isString(areaTag));
|
assert(_.isString(areaTag));
|
||||||
assert(_.isDate(modTimestamp) || moment.isMoment(modTimestamp));
|
assert(_.isDate(modTimestamp) || moment.isMoment(modTimestamp));
|
||||||
assert(_.isString(subject));
|
assert(_.isString(subject));
|
||||||
|
@ -181,9 +187,163 @@ Message.createMessageUUID = function(areaTag, modTimestamp, subject, body) {
|
||||||
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
|
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
|
||||||
|
|
||||||
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
|
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.getMessageIdByUuid = function(uuid, cb) {
|
/*
|
||||||
|
Find message IDs or UUIDs by filter. Available filters/options:
|
||||||
|
|
||||||
|
filter.uuids - use with resultType='id'
|
||||||
|
filter.ids - use with resultType='uuid'
|
||||||
|
filter.toUserName
|
||||||
|
filter.fromUserName
|
||||||
|
filter.replyToMesageId
|
||||||
|
filter.newerThanTimestamp
|
||||||
|
filter.newerThanMessageId
|
||||||
|
*filter.confTag - all area tags in confTag
|
||||||
|
filter.areaTag
|
||||||
|
*filter.metaTuples - {category, name, value}
|
||||||
|
|
||||||
|
*filter.terms - FTS search
|
||||||
|
|
||||||
|
filter.sort = modTimestamp | messageId
|
||||||
|
filter.order = ascending | (descending)
|
||||||
|
|
||||||
|
filter.limit
|
||||||
|
filter.resultType = (id) | uuid | count
|
||||||
|
filter.extraFields = []
|
||||||
|
|
||||||
|
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
|
||||||
|
(any other areaTag or confTag filters will be ignored)
|
||||||
|
|
||||||
|
*=NYI
|
||||||
|
*/
|
||||||
|
static findMessages(filter, cb) {
|
||||||
|
filter = filter || {};
|
||||||
|
|
||||||
|
filter.resultType = filter.resultType || 'id';
|
||||||
|
filter.extraFields = filter.extraFields || [];
|
||||||
|
|
||||||
|
const field = 'id' === filter.resultType ? 'message_id' : 'message_uuid';
|
||||||
|
|
||||||
|
if(moment.isMoment(filter.newerThanTimestamp)) {
|
||||||
|
filter.newerThanTimestamp = getISOTimestampString(filter.newerThanTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql;
|
||||||
|
if('count' === filter.resultType) {
|
||||||
|
sql =
|
||||||
|
`SELECT COUNT() AS count
|
||||||
|
FROM message m`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sql =
|
||||||
|
`SELECT DISTINCT m.${field}${filter.extraFields.length > 0 ? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ') : ''}
|
||||||
|
FROM message m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlOrderDir = 'ascending' === filter.order ? 'ASC' : 'DESC';
|
||||||
|
let sqlOrderBy;
|
||||||
|
let sqlWhere = '';
|
||||||
|
|
||||||
|
function appendWhereClause(clause) {
|
||||||
|
if(sqlWhere) {
|
||||||
|
sqlWhere += ' AND ';
|
||||||
|
} else {
|
||||||
|
sqlWhere += ' WHERE ';
|
||||||
|
}
|
||||||
|
sqlWhere += clause;
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently only avail sort
|
||||||
|
if('modTimestamp' === filter.sort) {
|
||||||
|
sqlOrderBy = `ORDER BY m.modified_timestamp ${sqlOrderDir}`;
|
||||||
|
} else {
|
||||||
|
sqlOrderBy = `ORDER BY m.message_id ${sqlOrderDir}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(filter.ids)) {
|
||||||
|
appendWhereClause(`m.message_id IN (${filter.ids.join(', ')})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(filter.uuids)) {
|
||||||
|
const uuidList = filter.uuids.map(u => `"${u}"`).join(', ');
|
||||||
|
appendWhereClause(`m.message_id IN (${uuidList})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(_.isNumber(filter.privateTagUserId)) {
|
||||||
|
appendWhereClause(`m.area_tag = "${Message.WellKnownAreaTags.Private}"`);
|
||||||
|
appendWhereClause(
|
||||||
|
`m.message_id IN (
|
||||||
|
SELECT message_id
|
||||||
|
FROM message_meta
|
||||||
|
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
|
||||||
|
)`);
|
||||||
|
} else {
|
||||||
|
let areaTags = [];
|
||||||
|
if(filter.confTag && filter.confTag.length > 0) {
|
||||||
|
// :TODO: grab areas from conf -> add to areaTags[]
|
||||||
|
}
|
||||||
|
|
||||||
|
if(areaTags.length > 0 || filter.areaTag && filter.areaTag.length > 0) {
|
||||||
|
if(Array.isArray(filter.areaTag)) {
|
||||||
|
areaTags = areaTags.concat(filter.areaTag);
|
||||||
|
} else if(_.isString(filter.areaTag)) {
|
||||||
|
areaTags.push(filter.areaTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
areaTags = _.uniq(areaTags); // remove any dupes
|
||||||
|
|
||||||
|
if(areaTags.length > 1) {
|
||||||
|
const areaList = filter.areaTag.map(t => `"${t}"`).join(', ');
|
||||||
|
appendWhereClause(`m.area_tag IN(${areaList})`);
|
||||||
|
} else {
|
||||||
|
appendWhereClause(`m.area_tag = "${areaTags[0]}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ 'toUserName', 'fromUserName', 'replyToMessageId' ].forEach(field => {
|
||||||
|
if(_.isString(filter[field]) && filter[field].length > 0) {
|
||||||
|
appendWhereClause(`m.${_.snakeCase(field)} = "${sanatizeString(filter[field])}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(_.isString(filter.newerThanTimestamp) && filter.newerThanTimestamp.length > 0) {
|
||||||
|
appendWhereClause(`DATETIME(m.modified_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_.isNumber(filter.newerThanMessageId)) {
|
||||||
|
appendWhereClause(`m.message_id > ${filter.newerThanMessageId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += `${sqlWhere} ${sqlOrderBy}`;
|
||||||
|
|
||||||
|
if(_.isNumber(filter.limit)) {
|
||||||
|
sql += ` LIMIT ${filter.limit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ';';
|
||||||
|
|
||||||
|
if('count' === filter.resultType) {
|
||||||
|
msgDb.get(sql, (err, row) => {
|
||||||
|
return cb(err, row ? row.count : 0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const matches = [];
|
||||||
|
const extra = filter.extraFields.length > 0;
|
||||||
|
msgDb.each(sql, (err, row) => {
|
||||||
|
if(_.isObject(row)) {
|
||||||
|
matches.push(extra ? row : row[field]);
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
return cb(err, matches);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: use findMessages, by uuid, limit=1
|
||||||
|
static getMessageIdByUuid(uuid, cb) {
|
||||||
msgDb.get(
|
msgDb.get(
|
||||||
`SELECT message_id
|
`SELECT message_id
|
||||||
FROM message
|
FROM message
|
||||||
|
@ -192,16 +352,20 @@ Message.getMessageIdByUuid = function(uuid, cb) {
|
||||||
[ uuid ],
|
[ uuid ],
|
||||||
(err, row) => {
|
(err, row) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
cb(err);
|
return cb(err);
|
||||||
} else {
|
|
||||||
const success = (row && row.message_id);
|
|
||||||
cb(success ? null : new Error('No match'), success ? row.message_id : null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const success = (row && row.message_id);
|
||||||
|
return cb(
|
||||||
|
success ? null : Errors.DoesNotExist(`No message for UUID ${uuid}`),
|
||||||
|
success ? row.message_id : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.getMessageIdsByMetaValue = function(category, name, value, cb) {
|
// :TODO: use findMessages
|
||||||
|
static getMessageIdsByMetaValue(category, name, value, cb) {
|
||||||
msgDb.all(
|
msgDb.all(
|
||||||
`SELECT message_id
|
`SELECT message_id
|
||||||
FROM message_meta
|
FROM message_meta
|
||||||
|
@ -209,15 +373,14 @@ Message.getMessageIdsByMetaValue = function(category, name, value, cb) {
|
||||||
[ category, name, value ],
|
[ category, name, value ],
|
||||||
(err, rows) => {
|
(err, rows) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
cb(err);
|
return cb(err);
|
||||||
} else {
|
|
||||||
cb(null, rows.map(r => parseInt(r.message_id))); // return array of ID(s)
|
|
||||||
}
|
}
|
||||||
|
return cb(null, rows.map(r => parseInt(r.message_id))); // return array of ID(s)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.getMetaValuesByMessageId = function(messageId, category, name, cb) {
|
static getMetaValuesByMessageId(messageId, category, name, cb) {
|
||||||
const sql =
|
const sql =
|
||||||
`SELECT meta_value
|
`SELECT meta_value
|
||||||
FROM message_meta
|
FROM message_meta
|
||||||
|
@ -229,7 +392,7 @@ Message.getMetaValuesByMessageId = function(messageId, category, name, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(0 === rows.length) {
|
if(0 === rows.length) {
|
||||||
return cb(new Error('No value for category/name'));
|
return cb(Errors.DoesNotExist('No value for category/name'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// single values are returned without an array
|
// single values are returned without an array
|
||||||
|
@ -237,31 +400,31 @@ Message.getMetaValuesByMessageId = function(messageId, category, name, cb) {
|
||||||
return cb(null, rows[0].meta_value);
|
return cb(null, rows[0].meta_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null, rows.map(r => r.meta_value)); // map to array of values only
|
return cb(null, rows.map(r => r.meta_value)); // map to array of values only
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.getMetaValuesByMessageUuid = function(uuid, category, name, cb) {
|
static getMetaValuesByMessageUuid(uuid, category, name, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getMessageId(callback) {
|
function getMessageId(callback) {
|
||||||
Message.getMessageIdByUuid(uuid, (err, messageId) => {
|
Message.getMessageIdByUuid(uuid, (err, messageId) => {
|
||||||
callback(err, messageId);
|
return callback(err, messageId);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getMetaValues(messageId, callback) {
|
function getMetaValues(messageId, callback) {
|
||||||
Message.getMetaValuesByMessageId(messageId, category, name, (err, values) => {
|
Message.getMetaValuesByMessageId(messageId, category, name, (err, values) => {
|
||||||
callback(err, values);
|
return callback(err, values);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, values) => {
|
(err, values) => {
|
||||||
cb(err, values);
|
return cb(err, values);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.loadMeta = function(cb) {
|
loadMeta(cb) {
|
||||||
/*
|
/*
|
||||||
Example of loaded this.meta:
|
Example of loaded this.meta:
|
||||||
|
|
||||||
|
@ -274,13 +437,12 @@ Message.prototype.loadMeta = function(cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const sql =
|
const sql =
|
||||||
`SELECT meta_category, meta_name, meta_value
|
`SELECT meta_category, meta_name, meta_value
|
||||||
FROM message_meta
|
FROM message_meta
|
||||||
WHERE message_id = ?;`;
|
WHERE message_id = ?;`;
|
||||||
|
|
||||||
let self = this;
|
const self = this; // :TODO: not required - arrow functions below:
|
||||||
msgDb.each(sql, [ this.messageId ], (err, row) => {
|
msgDb.each(sql, [ this.messageId ], (err, row) => {
|
||||||
if(!(row.meta_category in self.meta)) {
|
if(!(row.meta_category in self.meta)) {
|
||||||
self.meta[row.meta_category] = { };
|
self.meta[row.meta_category] = { };
|
||||||
|
@ -297,31 +459,32 @@ Message.prototype.loadMeta = function(cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.load = function(options, cb) {
|
load(options, cb) {
|
||||||
assert(_.isString(options.uuid));
|
assert(_.isString(options.uuid));
|
||||||
|
|
||||||
var self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadMessage(callback) {
|
function loadMessage(callback) {
|
||||||
msgDb.get(
|
msgDb.get(
|
||||||
'SELECT message_id, area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, ' +
|
`SELECT message_id, area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject,
|
||||||
'message, modified_timestamp, view_count ' +
|
message, modified_timestamp, view_count
|
||||||
'FROM message ' +
|
FROM message
|
||||||
'WHERE message_uuid=? ' +
|
WHERE message_uuid=?
|
||||||
'LIMIT 1;',
|
LIMIT 1;`,
|
||||||
[ options.uuid ],
|
[ options.uuid ],
|
||||||
(err, msgRow) => {
|
(err, msgRow) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!msgRow) {
|
if(!msgRow) {
|
||||||
return callback(new Error('Message (no longer) available'));
|
return callback(Errors.DoesNotExist('Message (no longer) available'));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.messageId = msgRow.message_id;
|
self.messageId = msgRow.message_id;
|
||||||
|
@ -333,29 +496,28 @@ Message.prototype.load = function(options, cb) {
|
||||||
self.subject = msgRow.subject;
|
self.subject = msgRow.subject;
|
||||||
self.message = msgRow.message;
|
self.message = msgRow.message;
|
||||||
self.modTimestamp = moment(msgRow.modified_timestamp);
|
self.modTimestamp = moment(msgRow.modified_timestamp);
|
||||||
self.viewCount = msgRow.view_count;
|
|
||||||
|
|
||||||
callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function loadMessageMeta(callback) {
|
function loadMessageMeta(callback) {
|
||||||
self.loadMeta(err => {
|
self.loadMeta(err => {
|
||||||
callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function loadHashTags(callback) {
|
function loadHashTags(callback) {
|
||||||
// :TODO:
|
// :TODO:
|
||||||
callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
err => {
|
||||||
cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.persistMetaValue = function(category, name, value, transOrDb, cb) {
|
persistMetaValue(category, name, value, transOrDb, cb) {
|
||||||
if(!_.isFunction(cb) && _.isFunction(transOrDb)) {
|
if(!_.isFunction(cb) && _.isFunction(transOrDb)) {
|
||||||
cb = transOrDb;
|
cb = transOrDb;
|
||||||
transOrDb = msgDb;
|
transOrDb = msgDb;
|
||||||
|
@ -369,21 +531,20 @@ Message.prototype.persistMetaValue = function(category, name, value, transOrDb,
|
||||||
value = [ value ];
|
value = [ value ];
|
||||||
}
|
}
|
||||||
|
|
||||||
let self = this;
|
const self = this;
|
||||||
|
|
||||||
async.each(value, (v, next) => {
|
async.each(value, (v, next) => {
|
||||||
metaStmt.run(self.messageId, category, name, v, err => {
|
metaStmt.run(self.messageId, category, name, v, err => {
|
||||||
next(err);
|
return next(err);
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.persist = function(cb) {
|
|
||||||
|
|
||||||
|
persist(cb) {
|
||||||
if(!this.isValid()) {
|
if(!this.isValid()) {
|
||||||
return cb(new Error('Cannot persist invalid message!'));
|
return cb(Errors.Invalid('Cannot persist invalid message!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -401,7 +562,8 @@ Message.prototype.persist = function(cb) {
|
||||||
self.areaTag,
|
self.areaTag,
|
||||||
msgTimestamp,
|
msgTimestamp,
|
||||||
self.subject,
|
self.subject,
|
||||||
self.message);
|
self.message
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
trans.run(
|
trans.run(
|
||||||
|
@ -436,14 +598,14 @@ Message.prototype.persist = function(cb) {
|
||||||
async.each(Object.keys(self.meta), (category, nextCat) => {
|
async.each(Object.keys(self.meta), (category, nextCat) => {
|
||||||
async.each(Object.keys(self.meta[category]), (name, nextName) => {
|
async.each(Object.keys(self.meta[category]), (name, nextName) => {
|
||||||
self.persistMetaValue(category, name, self.meta[category][name], trans, err => {
|
self.persistMetaValue(category, name, self.meta[category][name], trans, err => {
|
||||||
nextName(err);
|
return nextName(err);
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
nextCat(err);
|
return nextCat(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
}, err => {
|
}, err => {
|
||||||
callback(err, trans);
|
return callback(err, trans);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function storeHashTags(trans, callback) {
|
function storeHashTags(trans, callback) {
|
||||||
|
@ -461,20 +623,21 @@ Message.prototype.persist = function(cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.getFTNQuotePrefix = function(source) {
|
// :TODO: FTN stuff doesn't have any business here
|
||||||
|
getFTNQuotePrefix(source) {
|
||||||
source = source || 'fromUserName';
|
source = source || 'fromUserName';
|
||||||
|
|
||||||
return ftnUtil.getQuotePrefix(this[source]);
|
return ftnUtil.getQuotePrefix(this[source]);
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.getTearLinePosition = function(input) {
|
getTearLinePosition(input) {
|
||||||
const m = input.match(/^--- .+$(?![\s\S]*^--- .+$)/m);
|
const m = input.match(/^--- .+$(?![\s\S]*^--- .+$)/m);
|
||||||
return m ? m.index : -1;
|
return m ? m.index : -1;
|
||||||
};
|
}
|
||||||
|
|
||||||
Message.prototype.getQuoteLines = function(options, cb) {
|
getQuoteLines(options, cb) {
|
||||||
if(!options.termWidth || !options.termHeight || !options.cols) {
|
if(!options.termWidth || !options.termHeight || !options.cols) {
|
||||||
return cb(Errors.MissingParam());
|
return cb(Errors.MissingParam());
|
||||||
}
|
}
|
||||||
|
@ -657,4 +820,5 @@ Message.prototype.getQuoteLines = function(options, cb) {
|
||||||
|
|
||||||
return cb(null, quoted, null, false);
|
return cb(null, quoted, null, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue