216 lines
7.3 KiB
JavaScript
216 lines
7.3 KiB
JavaScript
const _ = require("lodash");
|
|
const async = require("async");
|
|
const redis = require("redis");
|
|
|
|
const config = require('config');
|
|
const Logger = require("./logger.js");
|
|
const UserMapping = require("./userMapping.js");
|
|
|
|
// The database of mappings. Uses the internal ID as key because it is unique
|
|
// unlike the external ID.
|
|
// Used always from memory, but saved to redis for persistence.
|
|
//
|
|
// Format:
|
|
// {
|
|
// internalMeetingID: {
|
|
// id: @id
|
|
// externalMeetingID: @externalMeetingID
|
|
// internalMeetingID: @internalMeetingID
|
|
// lastActivity: @lastActivity
|
|
// }
|
|
// }
|
|
// Format on redis:
|
|
// * a SET "...:mappings" with all ids (not meeting ids, the object id)
|
|
// * a HASH "...:mapping:<id>" for each mapping with all its attributes
|
|
const db = {};
|
|
let nextID = 1;
|
|
|
|
// A simple model to store mappings for meeting IDs.
|
|
module.exports = class IDMapping {
|
|
|
|
constructor() {
|
|
this.id = null;
|
|
this.externalMeetingID = null;
|
|
this.internalMeetingID = null;
|
|
this.lastActivity = null;
|
|
this.redisClient = Application.redisClient();
|
|
}
|
|
|
|
save(callback) {
|
|
this.redisClient.hmset(config.get("redis.keys.mappingPrefix") + ":" + this.id, this.toRedis(), (error, reply) => {
|
|
if (error != null) { Logger.error("[IDMapping] error saving mapping to redis:", error, reply); }
|
|
this.redisClient.sadd(config.get("redis.keys.mappings"), this.id, (error, reply) => {
|
|
if (error != null) { Logger.error("[IDMapping] error saving mapping ID to the list of mappings:", error, reply); }
|
|
|
|
db[this.internalMeetingID] = this;
|
|
(typeof callback === 'function' ? callback(error, db[this.internalMeetingID]) : undefined);
|
|
});
|
|
});
|
|
}
|
|
|
|
destroy(callback) {
|
|
this.redisClient.srem(config.get("redis.keys.mappings"), this.id, (error, reply) => {
|
|
if (error != null) { Logger.error("[IDMapping] error removing mapping ID from the list of mappings:", error, reply); }
|
|
this.redisClient.del(config.get("redis.keys.mappingPrefix") + ":" + this.id, error => {
|
|
if (error != null) { Logger.error("[IDMapping] error removing mapping from redis:", error); }
|
|
|
|
if (db[this.internalMeetingID]) {
|
|
delete db[this.internalMeetingID];
|
|
(typeof callback === 'function' ? callback(error, true) : undefined);
|
|
} else {
|
|
(typeof callback === 'function' ? callback(error, false) : undefined);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
toRedis() {
|
|
const r = {
|
|
"id": this.id,
|
|
"internalMeetingID": this.internalMeetingID,
|
|
"externalMeetingID": this.externalMeetingID,
|
|
"lastActivity": this.lastActivity
|
|
};
|
|
return r;
|
|
}
|
|
|
|
fromRedis(redisData) {
|
|
this.id = parseInt(redisData.id);
|
|
this.externalMeetingID = redisData.externalMeetingID;
|
|
this.internalMeetingID = redisData.internalMeetingID;
|
|
this.lastActivity = redisData.lastActivity;
|
|
}
|
|
|
|
print() {
|
|
return JSON.stringify(this.toRedis());
|
|
}
|
|
|
|
static addOrUpdateMapping(internalMeetingID, externalMeetingID, callback) {
|
|
let mapping = new IDMapping();
|
|
mapping.id = nextID++;
|
|
mapping.internalMeetingID = internalMeetingID;
|
|
mapping.externalMeetingID = externalMeetingID;
|
|
mapping.lastActivity = new Date().getTime();
|
|
mapping.save(function(error, result) {
|
|
Logger.info(`[IDMapping] added or changed meeting mapping to the list ${externalMeetingID}:`, mapping.print());
|
|
(typeof callback === 'function' ? callback(error, result) : undefined);
|
|
});
|
|
}
|
|
|
|
static removeMapping(internalMeetingID, callback) {
|
|
return (() => {
|
|
let result = [];
|
|
for (let internal in db) {
|
|
var mapping = db[internal];
|
|
if (mapping.internalMeetingID === internalMeetingID) {
|
|
result.push(mapping.destroy( (error, result) => {
|
|
Logger.info(`[IDMapping] removing meeting mapping from the list ${external}:`, mapping.print());
|
|
return (typeof callback === 'function' ? callback(error, result) : undefined);
|
|
}));
|
|
} else {
|
|
result.push(undefined);
|
|
}
|
|
}
|
|
return result;
|
|
})();
|
|
}
|
|
|
|
static getInternalMeetingID(externalMeetingID) {
|
|
const mapping = IDMapping.findByExternalMeetingID(externalMeetingID);
|
|
return (mapping != null ? mapping.internalMeetingID : undefined);
|
|
}
|
|
|
|
static getExternalMeetingID(internalMeetingID) {
|
|
if (db[internalMeetingID]){
|
|
return db[internalMeetingID].externalMeetingID;
|
|
}
|
|
}
|
|
|
|
static findByExternalMeetingID(externalMeetingID) {
|
|
if (externalMeetingID != null) {
|
|
for (let internal in db) {
|
|
const mapping = db[internal];
|
|
if (mapping.externalMeetingID === externalMeetingID) {
|
|
return mapping;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static allSync() {
|
|
let arr = Object.keys(db).reduce(function(arr, id) {
|
|
arr.push(db[id]);
|
|
return arr;
|
|
}
|
|
, []);
|
|
return arr;
|
|
}
|
|
|
|
// Sets the last activity of the mapping for `internalMeetingID` to now.
|
|
static reportActivity(internalMeetingID) {
|
|
let mapping = db[internalMeetingID];
|
|
if (mapping != null) {
|
|
mapping.lastActivity = new Date().getTime();
|
|
return mapping.save();
|
|
}
|
|
}
|
|
|
|
// Checks all current mappings for their last activity and removes the ones that
|
|
// are "expired", that had their last activity too long ago.
|
|
static cleanup() {
|
|
const now = new Date().getTime();
|
|
const all = IDMapping.allSync();
|
|
const toRemove = _.filter(all, mapping => mapping.lastActivity < (now - config.get("mappings.timeout")));
|
|
if (!_.isEmpty(toRemove)) {
|
|
Logger.info("[IDMapping] expiring the mappings:", _.map(toRemove, map => map.print()));
|
|
toRemove.forEach(mapping => {
|
|
UserMapping.removeMappingMeetingId(mapping.internalMeetingID);
|
|
mapping.destroy()
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initializes global methods for this model.
|
|
static initialize(callback) {
|
|
IDMapping.resync(callback);
|
|
IDMapping.cleanupInterval = setInterval(IDMapping.cleanup, config.get("mappings.cleanupInterval"));
|
|
}
|
|
|
|
// Gets all mappings from redis to populate the local database.
|
|
// Calls `callback()` when done.
|
|
static resync(callback) {
|
|
let client = Application.redisClient();
|
|
let tasks = [];
|
|
|
|
return client.smembers(config.get("redis.keys.mappings"), (error, mappings) => {
|
|
if (error != null) { Logger.error("[IDMapping] error getting list of mappings from redis:", error); }
|
|
|
|
mappings.forEach(id => {
|
|
tasks.push(done => {
|
|
client.hgetall(config.get("redis.keys.mappingPrefix") + ":" + id, function(error, mappingData) {
|
|
if (error != null) { Logger.error("[IDMapping] error getting information for a mapping from redis:", error); }
|
|
|
|
if (mappingData != null) {
|
|
let mapping = new IDMapping();
|
|
mapping.fromRedis(mappingData);
|
|
mapping.save(function(error, hook) {
|
|
if (mapping.id >= nextID) { nextID = mapping.id + 1; }
|
|
done(null, mapping);
|
|
});
|
|
} else {
|
|
done(null, null);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
return async.series(tasks, function(errors, result) {
|
|
mappings = _.map(IDMapping.allSync(), m => m.print());
|
|
Logger.info("[IDMapping] finished resync, mappings registered:", mappings);
|
|
return (typeof callback === 'function' ? callback() : undefined);
|
|
});
|
|
});
|
|
}
|
|
};
|