-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathid_mapping.js
215 lines (190 loc) · 7.26 KB
/
id_mapping.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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);
});
});
}
};