1 | /**
|
2 | * vim:set sw=2 ts=2 sts=2 ft=javascript expandtab:
|
3 | *
|
4 | * # Group Model
|
5 | *
|
6 | * ## License
|
7 | *
|
8 | * Licensed to the Apache Software Foundation (ASF) under one
|
9 | * or more contributor license agreements. See the NOTICE file
|
10 | * distributed with this work for additional information
|
11 | * regarding copyright ownership. The ASF licenses this file
|
12 | * to you under the Apache License, Version 2.0 (the
|
13 | * "License"); you may not use this file except in compliance
|
14 | * with the License. You may obtain a copy of the License at
|
15 | *
|
16 | * http://www.apache.org/licenses/LICENSE-2.0
|
17 | *
|
18 | * Unless required by applicable law or agreed to in writing,
|
19 | * software distributed under the License is distributed on an
|
20 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
21 | * KIND, either express or implied. See the License for the
|
22 | * specific language governing permissions and limitations
|
23 | * under the License.
|
24 | */
|
25 |
|
26 | module.exports = (function () {
|
27 | ;
|
28 |
|
29 | // Dependencies
|
30 | var ld = require('lodash');
|
31 | var cuid = require('cuid');
|
32 | var slugg = require('slugg');
|
33 | var asyncMod = require('async');
|
34 | var storage = require('../storage.js');
|
35 | var common = require('./common.js');
|
36 | var commonGroupPad = require ('./common-group-pad.js');
|
37 | var userCache = require('./user-cache.js');
|
38 | var deletePad = require('./pad.js').del;
|
39 | var GPREFIX = storage.DBPREFIX.GROUP;
|
40 | var UPREFIX = storage.DBPREFIX.USER;
|
41 | var PPREFIX = storage.DBPREFIX.PAD;
|
42 |
|
43 | /**
|
44 | * ## Description
|
45 | *
|
46 | * Groups belongs to users. Each user can have multiple groups of pads.
|
47 | *
|
48 | * A group object can be represented like :
|
49 | *
|
50 | * var group = {
|
51 | * _id: 'autoGeneratedUniqueString',
|
52 | * name: 'group1',
|
53 | * pads: [ 'padkey1', 'padkey2' ],
|
54 | * admins: [ 'userkey1', 'userkey2' ],
|
55 | * users: [ 'ukey1' ],
|
56 | * visibility: 'restricted' || 'public' || 'private',
|
57 | * password: 'secret',
|
58 | * readonly: false,
|
59 | * tags: ['important', 'domain1'],
|
60 | * allowUsersToCreatePads: false,
|
61 | * archived: false
|
62 | * };
|
63 | *
|
64 | */
|
65 |
|
66 | var group = {};
|
67 |
|
68 | /**
|
69 | * ## Public Functions
|
70 | *
|
71 | * ### get
|
72 | *
|
73 | * Group reading
|
74 | *
|
75 | * This function uses `common.getDel` with `del` to *false* and `GPREFIX`
|
76 | * fixed. It will takes mandatory key string and callback function. See
|
77 | * `common.getDel` for documentation.
|
78 | */
|
79 |
|
80 | group.get = ld.partial(common.getDel, false, GPREFIX);
|
81 |
|
82 | /**
|
83 | * ## getWithPads
|
84 | *
|
85 | * This function uses `group.get` to retrieve the group record, plus it
|
86 | * returns list of attached pads. As `group.get`, it takes `gid` group unique
|
87 | * identifier and a `callback` function. In case of success, it returns
|
88 | * *null*, the *group* object and an Object of *pads* (where keys are _ids).
|
89 | */
|
90 |
|
91 | group.getWithPads = function (gid, callback) {
|
92 | if (!ld.isString(gid)) {
|
93 | throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
|
94 | }
|
95 | if (!ld.isFunction(callback)) {
|
96 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
97 | }
|
98 | group.get(gid, function (err, g) {
|
99 | if (err) { return callback(err); }
|
100 | if (g.pads.length === 0) {
|
101 | return callback(null, g, {});
|
102 | }
|
103 | var padsKeys = ld.map(g.pads, function (p) { return PPREFIX + p; });
|
104 | storage.fn.getKeys(padsKeys, function (err, pads) {
|
105 | if (err) { return callback(err); }
|
106 | pads = ld.reduce(pads, function (memo, val, key) {
|
107 | key = key.substr(PPREFIX.length);
|
108 | memo[key] = val;
|
109 | return memo;
|
110 | }, {});
|
111 | return callback(null, g, pads);
|
112 | });
|
113 | });
|
114 | };
|
115 |
|
116 | /**
|
117 | * ### getByUser
|
118 | *
|
119 | * `getByUser` is an asynchronous function that returns all groups for a
|
120 | * defined user, using `storage.fn.getKeys`. It takes :
|
121 | *
|
122 | * - a `user` object
|
123 | * - a `withExtra` boolean, for gathering or not pads information alongside
|
124 | * with groups, with the help of `getPadsAndUsersByGroups` private function
|
125 | * - a `callback` function, called with *error* if needed, *null* and the
|
126 | * results, an object with keys and groups values, otherwise.
|
127 | *
|
128 | */
|
129 |
|
130 | group.getByUser = function (user, withExtra, callback) {
|
131 | if (!ld.isObject(user) || !ld.isArray(user.groups)) {
|
132 | throw new TypeError('BACKEND.ERROR.TYPE.USER_INVALID');
|
133 | }
|
134 | if (!ld.isBoolean(withExtra)) {
|
135 | throw new TypeError('BACKEND.ERROR.TYPE.WITHEXTRA_BOOL');
|
136 | }
|
137 | if (!ld.isFunction(callback)) {
|
138 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
139 | }
|
140 | storage.fn.getKeys(
|
141 | ld.map(user.groups, function (g) { return GPREFIX + g; }),
|
142 | function (err, groups) {
|
143 | if (err) { return callback(err); }
|
144 | groups = ld.reduce(groups, function (memo, val, key) {
|
145 | key = key.substr(GPREFIX.length);
|
146 | memo[key] = val;
|
147 | return memo;
|
148 | }, {});
|
149 | if (withExtra) {
|
150 | group.fn.getPadsAndUsersByGroups(groups, callback);
|
151 | } else {
|
152 | callback(null, groups);
|
153 | }
|
154 | }
|
155 | );
|
156 | };
|
157 |
|
158 | /**
|
159 | * ### getBookmarkedGroupsByUser
|
160 | *
|
161 | * `getBookmarkedGroupsByUser` is an asynchronous function that returns all
|
162 | * bookmarked groups for a defined user, using `storage.fn.getKeys`. It takes :
|
163 | *
|
164 | * - a `user` object
|
165 | * - a `callback` function, called with *error* if needed, *null* and the
|
166 | * results, an object with keys and groups values, otherwise.
|
167 | *
|
168 | */
|
169 |
|
170 | group.getBookmarkedGroupsByUser = function (user, callback) {
|
171 | if (!ld.isObject(user) || !ld.isArray(user.bookmarks.groups)) {
|
172 | throw new TypeError('BACKEND.ERROR.TYPE.USER_INVALID');
|
173 | }
|
174 | if (!ld.isFunction(callback)) {
|
175 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
176 | }
|
177 | storage.fn.getKeys(
|
178 | ld.map(user.bookmarks.groups, function (g) { return GPREFIX + g; }),
|
179 | function (err, groups) {
|
180 | if (err) { return callback(err); }
|
181 | groups = ld.reduce(groups, function (memo, val, key) {
|
182 | key = key.substr(GPREFIX.length);
|
183 | memo[key] = val;
|
184 | return memo;
|
185 | }, {});
|
186 | callback(null, groups);
|
187 | }
|
188 | );
|
189 | };
|
190 |
|
191 | /**
|
192 | * ### set
|
193 | *
|
194 | * This function adds a new group or updates an existing one.
|
195 | * It checks the fields, throws error if needed, set defaults options. As
|
196 | * arguments, it takes mandatory :
|
197 | *
|
198 | * - `params` object, with
|
199 | *
|
200 | * - a `name` string that can't be empty
|
201 | * - an optional `description` string
|
202 | * - an `admin` string, the unique key identifying the initial administrator
|
203 | * of the group
|
204 | * - `visibility`, a string defined as *restricted* by default to invited
|
205 | * users. Can be set to *public*, letting non authenticated users access to
|
206 | * all pads in the group with the URL, or *private*, protected by a password
|
207 | * phrase chosen by the administrator
|
208 | * - `readonly`, *false* on creation. If *true*, pads that will be linked to
|
209 | * the group will be set on readonly mode
|
210 | * - `password` string field, only usefull if visibility has been fixed to
|
211 | * private, by default an empty string
|
212 | * - `users` and `admins` arrays, with ids of users invited to read and/or edit
|
213 | * pads, for restricted visibility only; and group administrators
|
214 | *
|
215 | * - `callback` function returning *Error* if error, *null* otherwise and the
|
216 | * group object.
|
217 | *
|
218 | * `set` creates an empty `pads` array in case of creation, otherwise it just
|
219 | * gets back old value. `pads` array contains ids of pads attached to the
|
220 | * group via `model.pad` creation or update. Also, `password` is repopulated
|
221 | * from old value if the group has already been set as *private* and no
|
222 | * `password` has been given.
|
223 | *
|
224 | * Finally, in case of new group, it sets the unique identifier to the name
|
225 | * slugged and suffixes by random id generator and uses a special ctime
|
226 | * field for epoch time.
|
227 | */
|
228 |
|
229 | group.set = function (params, callback) {
|
230 | common.addSetInit(params, callback, ['name', 'admin']);
|
231 | var g = group.fn.assignProps(params);
|
232 | var check = function () {
|
233 | commonGroupPad.handlePassword(g, function (err, password) {
|
234 | if (err) { return callback(err); }
|
235 | if (password) { g.password = password; }
|
236 | group.fn.checkSet(g, callback);
|
237 | });
|
238 | };
|
239 | if (params._id) {
|
240 | g._id = params._id;
|
241 | storage.db.get(GPREFIX + g._id, function (err, res) {
|
242 | if (err) { return callback(err); }
|
243 | if (!res) {
|
244 | return callback(new Error('BACKEND.ERROR.GROUP.INEXISTENT'));
|
245 | }
|
246 | g.pads = res.pads;
|
247 | if ((res.visibility === 'private') && !g.password) {
|
248 | g.password = res.password;
|
249 | }
|
250 | g.ctime = res.ctime;
|
251 | check();
|
252 | });
|
253 | } else {
|
254 | g._id = (slugg(g.name) + '-' + cuid.slug());
|
255 | g.pads = [];
|
256 | g.ctime = Date.now();
|
257 | check();
|
258 | }
|
259 | };
|
260 |
|
261 |
|
262 | /**
|
263 | * ### del
|
264 | *
|
265 | * Group removal
|
266 | *
|
267 | * This function uses `common.getDel` with `del` to *true* and *GPREFIX*
|
268 | * fixed. It will takes mandatory key string and callback function. See
|
269 | * `common.getDel` for documentation.
|
270 | *
|
271 | * It uses the `callback` function to handle secondary indexes for users and
|
272 | * pads.
|
273 | */
|
274 |
|
275 | group.del = function (key, callback) {
|
276 | if (!ld.isFunction(callback)) {
|
277 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
278 | }
|
279 | common.getDel(false, GPREFIX, key, function (err, gr) {
|
280 | if (err) { return callback(err); }
|
281 | group.fn.cascadePads(gr, function(err) {
|
282 | if (err) { return callback(err); }
|
283 | common.getDel(true, GPREFIX, key, function (err, g) {
|
284 | if (err) { return callback(err); }
|
285 | var uids = ld.union(g.admins, g.users);
|
286 | group.fn.indexUsers(true, g._id, uids, callback);
|
287 | });
|
288 | });
|
289 | });
|
290 | };
|
291 |
|
292 | /**
|
293 | * ### resign
|
294 | *
|
295 | * `resign` os an asynchronous function that resigns current user from the
|
296 | * given group. It checks if the user is currently a user or administrator of
|
297 | * the group and accept resignation, except if the user is the unique
|
298 | * administrator. It takes care of internal index for the user.
|
299 | *
|
300 | * It takes :
|
301 | *
|
302 | * - `gid` group unique identifier;
|
303 | * - 'uid' user unique identifier;
|
304 | * - `callback` function calling with *error* if error or *null* and the
|
305 | * updated group otherwise.
|
306 | */
|
307 |
|
308 | group.resign = function (gid, uid, callback) {
|
309 | if (!ld.isString(gid) || !(ld.isString(uid))) {
|
310 | throw new TypeError('BACKEND.ERROR.TYPE.ID_STR');
|
311 | }
|
312 | if (!ld.isFunction(callback)) {
|
313 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
314 | }
|
315 | group.get(gid, function (err, g) {
|
316 | if (err) { return callback(err); }
|
317 | var users = ld.union(g.admins, g.users);
|
318 | if (!ld.includes(users, uid)) {
|
319 | return callback(new Error('BACKEND.ERROR.GROUP.NOT_USER'));
|
320 | }
|
321 | if ((ld.size(g.admins) === 1) && (ld.first(g.admins) === uid)) {
|
322 | return callback(new Error('BACKEND.ERROR.GROUP.RESIGN_UNIQUE_ADMIN'));
|
323 | }
|
324 | ld.pull(g.admins, uid);
|
325 | ld.pull(g.users, uid);
|
326 | storage.db.set(GPREFIX + g._id, g, function (err) {
|
327 | if (err) { return callback(err); }
|
328 | storage.db.get(UPREFIX + uid, function (err, u) {
|
329 | if (err) { return callback(err); }
|
330 | ld.pull(u.groups, gid);
|
331 | ld.pull(u.bookmarks.groups, gid);
|
332 | storage.db.set(UPREFIX + uid, u, function (err) {
|
333 | if (err) { return callback(err); }
|
334 | return callback(null, g);
|
335 | });
|
336 | });
|
337 | });
|
338 | });
|
339 | };
|
340 |
|
341 | /**
|
342 | * ### inviteOrShare
|
343 | *
|
344 | * `inviteOrShare` is an asynchronous function that check if given data, users
|
345 | * or admins logins, are correct and transforms it to expected values : unique
|
346 | * identifiers, before saving it to database.
|
347 | *
|
348 | * It takes :
|
349 | *
|
350 | * - `invite` boolean, *true* for user invitation, *false* for admin sharing;
|
351 | * - `gid` group unique identifier;
|
352 | * - array of users `loginsOrEmails`;
|
353 | * - `callback` function calling with *error* if error or *null* and the
|
354 | * updated group otherwise, plus accepted and refused invitations logins or
|
355 | * emails.
|
356 | *
|
357 | * It takes care of exclusion of admins and users : admin status is a
|
358 | * escalation of user.
|
359 | *
|
360 | * As login list should be exhaustive, it also takes care or reindexing user
|
361 | * local groups.
|
362 | */
|
363 |
|
364 | group.inviteOrShare = function (invite, gid, loginsOrEmails, callback) {
|
365 | if (!ld.isBoolean(invite)) {
|
366 | throw new TypeError('BACKEND.ERROR.TYPE.INVITE_BOOL');
|
367 | }
|
368 | if (!ld.isString(gid)) {
|
369 | throw new TypeError('BACKEND.ERROR.TYPE.GID_STR');
|
370 | }
|
371 | if (!ld.isArray(loginsOrEmails)) {
|
372 | throw new TypeError('BACKEND.ERROR.TYPE.LOGINS_ARR');
|
373 | }
|
374 | if (!ld.isFunction(callback)) {
|
375 | throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
|
376 | }
|
377 | var users = userCache.fn.getIdsFromLoginsOrEmails(loginsOrEmails);
|
378 | group.get(gid, function (err, g) {
|
379 | if (err) { return callback(err); }
|
380 | var removed;
|
381 | if (invite) {
|
382 | // Remove users from admin before setting them as invited
|
383 | var toRemoveFromAdmins = ld.intersection(g.admins, users.uids);
|
384 | g.admins = ld.filter(g.admins, function(n) {
|
385 | return (ld.indexOf(toRemoveFromAdmins, n) === -1);
|
386 | });
|
387 | if (ld.size(g.admins) === 0) {
|
388 | return callback(
|
389 | new Error('BACKEND.ERROR.GROUP.RESIGN_UNIQUE_ADMIN')
|
390 | );
|
391 | }
|
392 |
|
393 | // Setting users as invited
|
394 | removed = ld.difference(g.users, users.uids);
|
395 | g.users = ld.unique(ld.reject(users.uids,
|
396 | ld.partial(ld.includes, g.admins)));
|
397 | } else {
|
398 | // Remove users from invite before setting them as admins
|
399 | var toRemoveFromUsers = ld.intersection(g.users, users.uids);
|
400 | g.users = ld.filter(g.users, function(n) {
|
401 | return (ld.indexOf(toRemoveFromUsers, n) === -1);
|
402 | });
|
403 |
|
404 | // Setting users as admins
|
405 | removed = ld.difference(g.admins, users.uids);
|
406 | g.admins = ld.unique(ld.reject(users.uids,
|
407 | ld.partial(ld.includes, g.users)));
|
408 | if ((ld.size(g.admins)) === 0) {
|
409 | return callback(
|
410 | new Error('BACKEND.ERROR.GROUP.RESIGN_UNIQUE_ADMIN')
|
411 | );
|
412 | }
|
413 | }
|
414 | // indexUsers with deletion for full reindexation process
|
415 | group.fn.indexUsers(true, g._id, removed, function (err) {
|
416 | if (err) { return callback(err); }
|
417 | group.fn.set(g, function (err, g) {
|
418 | if (err) { return callback(err); }
|
419 | callback(null, g, ld.omit(users, 'uids'));
|
420 | });
|
421 | });
|
422 | });
|
423 | };
|
424 |
|
425 | /**
|
426 | * ## Helper Functions
|
427 | *
|
428 | * Helper here are public functions created to facilitate interaction with
|
429 | * the API and improve performance, avoiding extra checking when not needed.
|
430 | * TODO : may be written to improve API usage
|
431 | */
|
432 |
|
433 | group.helper = {};
|
434 |
|
435 | /**
|
436 | * ### linkPads
|
437 | *
|
438 | * `linkPads` is a function to attach new pads to an existing group.
|
439 | * It takes mandatory arguments :
|
440 | *
|
441 | * - the pad `_id`entifier, a string
|
442 | * - `add`, a string for only one addition, an array for multiple adds.
|
443 | */
|
444 |
|
445 | group.helper.linkPads = ld.noop;
|
446 |
|
447 | group.helper.unlinkPads = ld.noop;
|
448 |
|
449 | /**
|
450 | * ### inviteUsers
|
451 | * string or array
|
452 | */
|
453 |
|
454 | group.helper.inviteUsers = ld.noop;
|
455 |
|
456 | /**
|
457 | * ### setAdmins
|
458 | * string or array
|
459 | */
|
460 |
|
461 | group.helper.setAdmins = ld.noop;
|
462 |
|
463 | /**
|
464 | * ### setPassword
|
465 | * string of false
|
466 | */
|
467 |
|
468 | group.helper.setPassword = ld.noop;
|
469 |
|
470 | /**
|
471 | * ### setPublic
|
472 | * boolean
|
473 | */
|
474 |
|
475 | group.helper.setPublic = ld.noop;
|
476 |
|
477 | /**
|
478 | * ### archive
|
479 | * boolean
|
480 | */
|
481 |
|
482 | group.helper.archive = ld.noop;
|
483 |
|
484 | /**
|
485 | * ## Internal Functions
|
486 | *
|
487 | * These functions are not private like with closures, for testing purposes,
|
488 | * but they are expected be used only internally by other MyPads functions.
|
489 | * All of these are tested through public API.
|
490 | */
|
491 |
|
492 | group.fn = {};
|
493 |
|
494 | /**
|
495 | * ### assignProps
|
496 | *
|
497 | * `assignProps` takes params object and assign defaults if needed.
|
498 | * It creates :
|
499 | *
|
500 | * - an `admins` array, unioning admin key to optional others admins
|
501 | * - a `users` array, empty or with given keys
|
502 | * - a `pads` array, empty on creation, can't be fixed either
|
503 | * - a `visibility` string, defaults to *restricted*, with only two other
|
504 | * possibilities : *private* or *public*
|
505 | * - a `password` string, *null* by default
|
506 | * - a `readonly` boolean, *false* by default
|
507 | *
|
508 | * It returns the group object.
|
509 | */
|
510 |
|
511 | group.fn.assignProps = function (params) {
|
512 | var p = params;
|
513 | var g = { name: p.name };
|
514 |
|
515 | g.description = (ld.isString(p.description) ? p.description : '');
|
516 | p.admins = ld.isArray(p.admins) ? ld.filter(p.admins, ld.isString) : [];
|
517 | g.admins = ld.union([ p.admin ], p.admins);
|
518 | g.users = ld.uniq(p.users);
|
519 |
|
520 | var v = p.visibility;
|
521 | var vVal = ['restricted', 'private', 'public'];
|
522 |
|
523 | g.visibility = (ld.isString(v) && ld.includes(vVal, v)) ? v : 'restricted';
|
524 | g.password = ld.isString(p.password) ? p.password : null;
|
525 | g.readonly = ld.isBoolean(p.readonly) ? p.readonly : false;
|
526 | g.allowUsersToCreatePads = ld.isBoolean(p.allowUsersToCreatePads) ? p.allowUsersToCreatePads : false;
|
527 | g.archived = ld.isBoolean(p.archived) ? p.archived : false;
|
528 | g.tags = ld.isArray(p.tags) ? p.tags : [];
|
529 | return g;
|
530 | };
|
531 |
|
532 | /**
|
533 | * ### cascadePads
|
534 | *
|
535 | * `cascadePads` is an asynchronous function which handle cascade removals
|
536 | * after group removal. It takes :
|
537 | *
|
538 | * - the `group` object
|
539 | * - a `callback` function, returning *Error* or *null* if succeeded
|
540 | */
|
541 |
|
542 | group.fn.cascadePads = function (group, callback) {
|
543 | if (!ld.isEmpty(group.pads)) {
|
544 | asyncMod.map(group.pads, deletePad, function(err, res) {
|
545 | if (err) { return callback(err); }
|
546 | var e = new Error('BACKEND.ERROR.GROUP.CASCADE_REMOVAL_PROBLEM');
|
547 | if (!res) { return callback(e); }
|
548 | callback(null);
|
549 | });
|
550 | } else {
|
551 | callback(null);
|
552 | }
|
553 | };
|
554 |
|
555 | /**
|
556 | * ### indexUsers
|
557 | *
|
558 | * `indexUsers` is an asynchronous function which handles secondary indexes
|
559 | * for *users.groups* after group creation, update, removal. It takes :
|
560 | *
|
561 | * - a `del` boolean to know if we have to delete key from index or add it
|
562 | * - the group `gid` unique identifier
|
563 | * - `uids`, an array of user keys
|
564 | * - a `callback` function, returning *Error* or *null* if succeeded
|
565 | */
|
566 |
|
567 | group.fn.indexUsers = function (del, gid, uids, callback) {
|
568 | var usersKeys = ld.map(uids, function (u) { return UPREFIX + u; });
|
569 | storage.fn.getKeys(usersKeys, function (err, users) {
|
570 | if (err) { return callback(err); }
|
571 | ld.forIn(users, function (u, k) {
|
572 | // When deleting the user, storage.fn.getKeys(usersKeys)
|
573 | // returns undefined because the user record has already
|
574 | // been deleted
|
575 | if (typeof(u) !== 'undefined') {
|
576 | if (del) {
|
577 | ld.pull(u.groups, gid);
|
578 | ld.pull(u.bookmarks.groups, gid);
|
579 | } else if (!ld.includes(u.groups, gid)) {
|
580 | u.groups.push(gid);
|
581 | }
|
582 | users[k] = u;
|
583 | }
|
584 | });
|
585 | storage.fn.setKeys(users, function (err) {
|
586 | if (err) { return callback(err); }
|
587 | return callback(null);
|
588 | });
|
589 | });
|
590 | };
|
591 |
|
592 | /**
|
593 | * ### set
|
594 | *
|
595 | * `set` is internal function that set the user group into the database.
|
596 | * It takes care of secondary indexes for users and pads by calling
|
597 | * `indexUsers`.
|
598 | *
|
599 | * It takes, as arguments :
|
600 | *
|
601 | * - the `g` group object
|
602 | * - the `callback` function returning an *Error* or *null* and the `g`
|
603 | * object.
|
604 | */
|
605 |
|
606 | group.fn.set = function (g, callback) {
|
607 | storage.db.set(GPREFIX + g._id, g, function (err) {
|
608 | if (err) { return callback(err); }
|
609 | var uids = ld.union(g.admins, g.users);
|
610 | group.fn.indexUsers(false, g._id, uids, function (err) {
|
611 | if (err) { return callback(err); }
|
612 | return callback(null, g);
|
613 | });
|
614 | });
|
615 | };
|
616 |
|
617 | /**
|
618 | * ### checkSet
|
619 | *
|
620 | * `checkSet` will ensure that all users and pads exist. If true, it calls
|
621 | * `fn.set`, else it will return an *Error*. `checkSet` takes :
|
622 | *
|
623 | * - a `g` group object
|
624 | * - a `callback` function returning an *Error* or *null* and the `g` object.
|
625 | */
|
626 |
|
627 | group.fn.checkSet = function (g, callback) {
|
628 | var pre = ld.curry(function (pre, val) { return pre + val; });
|
629 | var admins = ld.map(g.admins, pre(UPREFIX));
|
630 | var users = ld.map(g.users, pre(UPREFIX));
|
631 | var pads = ld.map(g.pads, pre(PPREFIX));
|
632 | var allKeys = ld.union(admins, users, pads);
|
633 | common.checkMultiExist(allKeys, function (err, res) {
|
634 | if (err) { return callback(err); }
|
635 | if (!res) {
|
636 | var e = new Error('BACKEND.ERROR.GROUP.ITEMS_NOT_FOUND');
|
637 | return callback(e);
|
638 | }
|
639 | group.fn.set(g, callback);
|
640 | });
|
641 | };
|
642 |
|
643 | /**
|
644 | * ### getPadsAndUsersByGroups
|
645 | *
|
646 | * `getPadsAndUsersByGroups` is an asynchronous private function which return
|
647 | * *pads* and *users* objects from an object of *group* objects (key: group).
|
648 | * It also takes a classic `callback` function.
|
649 | */
|
650 |
|
651 | group.fn.getPadsAndUsersByGroups = function (groups, callback) {
|
652 | var defs = { pads: PPREFIX, users: UPREFIX };
|
653 | var addPfx = function (pfx, values) {
|
654 | return ld.map(values, function (v) { return pfx + v; });
|
655 | };
|
656 | var keys = ld.reduce(groups, function (memo, val) {
|
657 | memo.pads = ld.union(memo.pads, addPfx(PPREFIX, val.pads));
|
658 | memo.users = ld.union(memo.users, addPfx(UPREFIX, val.users),
|
659 | addPfx(UPREFIX, val.admins));
|
660 | return memo;
|
661 | }, { pads: [], users: [] });
|
662 | storage.fn.getKeys(ld.flatten(ld.values(keys)), function (err, res) {
|
663 | if (err) { return callback(err); }
|
664 | res = ld.reduce(res, function (memo, val, key) {
|
665 | var field;
|
666 | ld.forIn(keys, function (vals, f) {
|
667 | if (ld.includes(vals, key)) { field = f; }
|
668 | });
|
669 | key = key.substr(defs[field].length);
|
670 | memo[field][key] = val;
|
671 | return memo;
|
672 | }, { groups: groups, pads: {}, users: {} });
|
673 | callback(null, res);
|
674 | });
|
675 | };
|
676 |
|
677 | /**
|
678 | * ### count
|
679 | *
|
680 | * Returns the number of groups of the MyPads instance
|
681 | * As arguments, it takes mandatory :
|
682 | * - a `callback` function
|
683 | */
|
684 |
|
685 | group.count = function(callback) {
|
686 | storage.db.findKeys(GPREFIX + '*', null, function (err, res) {
|
687 | if (err) { return callback(err); }
|
688 | return callback(null, ld.size(res));
|
689 | });
|
690 | };
|
691 |
|
692 | return group;
|
693 |
|
694 |
|
695 | }).call(this);
|