1 /**
  2  * @fileOverview Application Resource class definition
  3  */
  4 var Resource = require('./resource')
  5   , Account = require('./account')
  6   , Channel = require('./channel')
  7   , Subscription = require('./subscription')
  8   , Member = require('./member')
  9   , _ = require('underscore')
 10   ;
 11 
 12 /**
 13  * Represents an application in the spire api.
 14  *
 15  * @class Application Resource
 16  *
 17  * @constructor
 18  * @extends Resource
 19  * @param {object} spire Spire object
 20  * @param {object} data  Application data from the spire api
 21  */
 22 function Application(spire, data) {
 23   /**
 24    * Reference to spire object.
 25    */
 26   this.spire = spire;
 27 
 28   /**
 29    * Actual data from the spire.io api.
 30    */
 31   this.data = data;
 32 
 33   this.resourceName = 'application';
 34 
 35   this._channels = {};
 36   this._subscriptions = {};
 37   this._members = {};
 38   this._membersCache = {};
 39   this._storeResources();
 40 }
 41 
 42 Application.prototype = new Resource();
 43 
 44 module.exports = Application;
 45 
 46 /**
 47  * Returns the application name.
 48  *
 49  * @returns {string} Application name
 50  */
 51 Application.prototype.name = function () {
 52   return this.data.name;
 53 };
 54 
 55 /**
 56  * Returns the application key.
 57  *
 58  * @returns {string} Application key
 59  */
 60 Application.prototype.key = function () {
 61   return this.data.key;
 62 };
 63 
 64 /**
 65  * Gets the members collection.  Returns a hash of Member resources.
 66  *
 67  * Returns a value from the cache, if one if available.
 68  *
 69  * @example
 70  * application.members({limit: 10},function (err, members) {
 71  *   if (!err) {
 72  *     // `members` is a hash of all the applications's members
 73  *   }
 74  * });
 75  *
 76  * @param Object options Options for retrieving members (limit and after)
 77  * @param {function (err, members)} cb Callback
 78  */
 79 Application.prototype.members = function (options, cb) {
 80   if(typeof cb == 'undefined'){
 81     cb = options;
 82     options = {};
 83   }
 84   var optString = this._optionsToString(options);
 85   if (this._membersCache[optString]) return cb(null, this._membersCache[optString]);
 86   this.members$(options, cb);
 87 };
 88 
 89 /**
 90  * Gets the members collection.  Returns a hash of Members resources.
 91  *
 92  * Always gets a fresh value from the api.
 93  *
 94  * @example
 95  * application.members$({limit: 10}, function (err, members) {
 96  *   if (!err) {
 97  *     // `members` is a hash of all the applications's members
 98  *   }
 99  * });
100  *
101  * @param Object options Options for retrieving members (limit and after)
102  * @param {function (err, members)} cb Callback
103  */
104 Application.prototype.members$ = function (options, cb) {
105   if(typeof cb == 'undefined'){
106     cb = options;
107     options = {};
108   }
109   var application = this,
110     optString = this._optionsToString(options);
111   this.request('members', options, function (err, membersData) {
112     if (err) return cb(err);
113     _.each(membersData, function (member, name) {
114       application._memoizeMember(new Member(application.spire, member), optString);
115     });
116     cb(null, application._members);
117   });
118 };
119 
120 /**
121  * Creates a member.  Returns a Member resource.  Errors if a member with the
122  * specified login exists.
123  *
124  * @param {object} parameters including login, password, and an optional email
125  * @param {function (err, member)} cb Callback
126  */
127 Application.prototype.createMember = function (data, cb) {
128   var application = this;
129   this.request('create_member', data, function (err, data) {
130     if (err) return cb(err);
131     var member = new Member(application.spire, data);
132     application._memoizeMember(member);
133     cb(null, member);
134   });
135 };
136 
137 /**
138  * Authenticates a member.  Returns a Member resource.  Error if authenication fails.
139  *
140  * @param {string} login Member login
141  * @param {string} password Member password
142  * @param {function (err, member)} cb Callback
143  */
144 Application.prototype.authenticateMember = function (login, password, cb) {
145   var application = this;
146   var params = {
147     login: login,
148     password: password
149   }
150   this.request('authenticate_member', params, function (err, data) {
151     if (err) return cb(err);
152     var member = new Member(application.spire, data);
153     application._memoizeMember(member);
154     cb(null, member);
155   });
156 };
157 
158 /**
159  * Resets a member password.  Empty response unless an error occurs.
160  *
161  * @param {string} email Member email or login
162  * @param {function (err)} cb Callback
163  */
164 Application.prototype.requestMemberPasswordReset = function (email, cb) {
165   var application = this;
166   this.request('reset_member_password', email, function (err, data) {
167     if (err) return cb(err);
168     cb(null);
169   });
170 };
171 
172 /**
173  * Resets a member password using a reset key.  Can also update the user password (optional).
174  * Error if authenication fails.
175  *
176  * @param {string} login Member login
177  * @param {string} password Member password
178  * @param {function (err, member)} cb Callback
179  */
180 Application.prototype.resetPassword = function (reset_key, new_password, cb) {
181   var application = this;
182   var params = {
183     reset_key: reset_key,
184     password: new_password
185   }
186   this.request('authenticate_member', params, function (err, data) {
187     if (err) return cb(err);
188     var member = new Member(application.spire, data);
189     application._memoizeMember(member);
190     cb(null, member);
191   });
192 };
193 
194 /**
195  * Gets a member by login.  Returns a Member resource
196  *
197  * Always gets a fresh value from the api.
198  *
199  * @example
200  * spire.session.memberByEmail('login_of_member', function (err, member) {
201  *   if (!err) {
202  *     // `member` now contains a member object
203  *   }
204  * });
205  *
206  * @param {String} memberEmail Email of member
207  * @param {function (err, member)} cb Callback
208  */
209 Application.prototype.memberByEmail = function (memberEmail, cb) {
210   var application = this;
211   this.request('member_by_login', function (err, memberData) {
212     if (err) return cb(err);
213     member = new Member(application.spire, memberData[memberEmail]);
214     application._memoizeMember(member);
215     cb(null, member);
216   });
217 };
218 
219 /**
220  * Gets the channels collection.  Returns a hash of Channel resources.
221  *
222  * Returns a value from the cache, if one if available.
223  *
224  * @example
225  * application.channels(function (err, channels) {
226  *   if (!err) {
227  *     // `channels` is a hash of all the applications's channels
228  *   }
229  * });
230  *
231  * @param {function (err, channels)} cb Callback
232  */
233 Application.prototype.channels = function (cb) {
234   if (!_.isEmpty(this._channels)) return cb(null, this._channels);
235   this.channels$(cb);
236 };
237 
238 /**
239  * Gets the channels collection.  Returns a hash of Channel resources.
240  *
241  * Always gets a fresh value from the api.
242  *
243  * @example
244  * application.channels$(function (err, channels) {
245  *   if (!err) {
246  *     // `channels` is a hash of all the applications's channels
247  *   }
248  * });
249  *
250  * @param {function (err, channels)} cb Callback
251  */
252 Application.prototype.channels$ = function (cb) {
253   var application = this;
254   this.request('channels', function (err, channelsData) {
255     if (err) return cb(err);
256     _.each(channelsData, function (channel, name) {
257       application._memoizeChannel(new Channel(application.spire, channel));
258     });
259     cb(null, application._channels);
260   });
261 };
262 
263 /**
264  * Gets a channel by name.  Returns a Channel resource
265  *
266  * Always gets a fresh value from the api.
267  *
268  * @example
269  * spire.session.channelByName('name_of_channel', function (err, channel) {
270  *   if (!err) {
271  *     // `channel` now contains a channel object
272  *   }
273  * });
274  *
275  * @param {String} channelName Name of channel
276  * @param {function (err, channel)} cb Callback
277  */
278 Application.prototype.channelByName = function (channelName, cb) {
279   var application = this;
280   this.request('channel_by_name', function (err, channelData) {
281     if (err) return cb(err);
282     channel = new Channel(application.spire, channelData[channelName]);
283     application._memoizeChannel(channel);
284     cb(null, channel);
285   });
286 };
287 
288 /**
289  * Gets the subscriptions collection.  Returns a hash of Subscription resources.
290  *
291  * Returns a value from the cache, if one if available.
292  *
293  * @example
294  * application.subscriptions(function (err, subscriptions) {
295  *   if (!err) {
296  *     // `subscriptions` is a hash of all the applications's subscriptions
297  *   }
298  * });
299  *
300  * @param {function (err, subscriptions)} cb Callback
301  */
302 Application.prototype.subscriptions = function (cb) {
303   if (!_.isEmpty(this._subscriptions)) return cb(null, this._subscriptions);
304   this.subscriptions$(cb);
305 };
306 
307 /**
308  * Gets the subscriptions collection.  Returns a hash of Subscription resources.
309  *
310  * Always gets a fresh value from the api.
311  *
312  * @example
313  * application.subscriptions$(function (err, subscriptions) {
314  *   if (!err) {
315  *     // `subscriptions` is a hash of all the application's subscriptions
316  *   }
317  * });
318  *
319  * @param {function (err, subscriptions)} cb Callback
320  */
321 Application.prototype.subscriptions$ = function (cb) {
322   var application = this;
323   this.request('subscriptions', function (err, subscriptions) {
324     if (err) return cb(err);
325     application._subscriptions = {};
326     _.each(subscriptions, function (subscription, name) {
327       application._memoizeSubscription(new Subscription(application.spire, subscription));
328     });
329     cb(null, application._subscriptions);
330   });
331 };
332 
333 /**
334  * Gets a subscription by name.  Returns a Subscription resource
335  *
336  * Always gets a fresh value from the api.
337  *
338  * @example
339  * spire.session.subscriptionByName('name_of_subscription', function (err, subscription) {
340  *   if (!err) {
341  *     // `subscription` now contains a subscription object
342  *   }
343  * });
344  *
345  * @param {String} subscriptionName Name of subscription
346  * @param {function (err, subscription)} cb Callback
347  */
348 Application.prototype.subscriptionByName = function (subscriptionName, cb) {
349   var application = this;
350   this.request('subscription_by_name', function (err, subscriptionData) {
351     if (err) return cb(err);
352     subscription = new Subscription(application.spire, subscriptionData[subscriptionName]);
353     application._memoizeSubscription(subscription);
354     cb(null, subscription);
355   });
356 };
357 
358 /**
359  * Creates a channel.  Returns a Channel resource.  Errors if a channel with the
360  * specified name exists.
361  *
362  * @param {string} name Channel name
363  * @param {function (err, channel)} cb Callback
364  */
365 Application.prototype.createChannel = function (name, cb) {
366   var application = this;
367   this.request('create_channel', name, function (err, data) {
368     if (err) return cb(err);
369     var channel = new Channel(application.spire, data);
370     application._memoizeChannel(channel);
371     cb(null, channel);
372   });
373 };
374 
375 /**
376  * Creates a subscription to any number of channels.  Returns a Subscription
377  * resource.  Errors if a subscription with the specified name exists.
378  *
379  * @param {object} options Options
380  * @param {string} options.name Subscription name
381  * @param {array} options.channelNames Channel names to subscribe to
382  * @param {array} options.channelUrls Channel urls to subscribe to
383  * @param {number} timeout Subscription timeout
384  * @param {function (err, subscription)} cb Callback
385  */
386 Application.prototype.createSubscription = function (options, cb) {
387   var name = options.name;
388   var channelNames = options.channelNames || [];
389   var channelUrls = options.channelUrls || [];
390   var timeout = options.timeout;
391 
392   var application = this;
393   this.channels(function (channels) {
394     channelUrls.push.apply(channelUrls, _.map(channelNames, function (name) {
395       return application._channels[name].url();
396     }));
397     application.request('create_subscription', name, channelUrls, timeout, function (err, sub) {
398       if (err) return cb(err);
399       var subscription = new Subscription(application.spire, sub);
400       application._memoizeSubscription(subscription);
401       cb(null, subscription);
402     });
403   });
404 };
405 
406  /**
407  * Stores the member resource in a hash by its name.
408  *
409  * @param channel {object} Member to store
410  */
411 Application.prototype._memoizeMember = function (member, cacheString) {
412   this._members[member.login] = member;
413   if(cacheString){
414     if(!this._membersCache[cacheString]){
415       this._membersCache[cacheString] = {};
416     }
417     this._membersCache[cacheString][member.login] = member;
418   }
419 };
420 
421  /**
422  * Stores the channel resource in a hash by its name.
423  *
424  * @param channel {object} Channel to store
425  */
426 Application.prototype._memoizeChannel = function (channel) {
427   this._channels[channel.name()] = channel;
428 };
429 
430 /**
431  * Stores the subscription resource in a hash by its name.
432  *
433  * @param subscription {object} Subscription to store
434  */
435 Application.prototype._memoizeSubscription = function (subscription) {
436   this._subscriptions[subscription.name()] = subscription;
437 };
438 
439 /**
440  * Stores the resources.
441  */
442 Application.prototype._storeResources = function () {
443   var application = this;
444   var resources = {};
445   _.each(this.data.resources, function (resource, name) {
446     // Turn the account object into an instance of Resource.
447     if (name === 'account') {
448       resource = new Account(application.spire, resource);
449       application._account = resource;
450     }
451     resources[name] = resource;
452   });
453 
454   this.resources = resources;
455 };
456 
457 Application.prototype._optionsToString = function (options){
458   return "l_" + options.limit + "_a_" + options.after;
459 };
460 
461 /**
462  * Requests
463  *
464  * These define API calls and have no side effects.  They can be run by calling
465  *     this.request(<request name>);
466  */
467 
468 /**
469  * Gets the channels collection.
470  * @name channels
471  * @ignore
472  */
473 Resource.defineRequest(Application.prototype, 'channels', function () {
474   var collection = this.data.resources.channels;
475   return {
476     method: 'get',
477     url: collection.url,
478     headers: {
479       'Authorization': this.authorization('all', collection),
480       'Accept': this.mediaType('channels')
481     }
482   };
483 });
484 
485 /**
486  * Gets a channel by name.  Returns a collection with a single value: { name: channel }.
487  * @name channel_by_name
488  * @ignore
489  */
490 Resource.defineRequest(Application.prototype, 'channel_by_name', function (name) {
491   var collection = this.data.resources.channels;
492   return {
493     method: 'get',
494     url: collection.url,
495     query: { name: name },
496     headers: {
497       'Authorization': this.authorization('get_by_name', collection),
498       'Accept': this.mediaType('channels')
499     }
500   };
501 });
502 
503 /**
504  * Creates a channel.  Returns a channel object.
505  * @name create_channel
506  * @ignore
507  */
508 Resource.defineRequest(Application.prototype, 'create_channel', function (name) {
509   var collection = this.data.resources.channels;
510   return {
511     method: 'post',
512     url: collection.url,
513     content: { name: name },
514     headers: {
515       'Authorization': this.authorization('create', collection),
516       'Accept': this.mediaType('channel'),
517       'Content-Type': this.mediaType('channel')
518     }
519   };
520 });
521 
522 /**
523  * Gets the subscriptions collection.
524  * @name subscrtiptions
525  * @ignore
526  */
527 Resource.defineRequest(Application.prototype, 'subscriptions', function () {
528   var collection = this.data.resources.subscriptions;
529   return {
530     method: 'get',
531     url: collection.url,
532     headers: {
533       'Authorization': this.authorization('all', collection),
534       'Accept': this.mediaType('subscriptions')
535     }
536   };
537 });
538 
539 /**
540  * Creates a subscrtiption.  Returns a subscription object.
541  * @name create_subscription
542  * @ignore
543  */
544 Resource.defineRequest(Application.prototype, 'create_subscription', function (name, channelUrls, timeout) {
545   var collection = this.data.resources.subscriptions;
546   return {
547     method: 'post',
548     url: collection.url,
549     content: {
550       name: name,
551       channels: channelUrls,
552       timeout: timeout
553     },
554     headers: {
555       'Authorization': this.authorization('create', collection),
556       'Accept': this.mediaType('subscription'),
557       'Content-Type': this.mediaType('subscription')
558     }
559   };
560 });
561 
562 /**
563  * Gets a subscription by name.  Returns a collection with a single value: { name: subscription }.
564  * @name subscription_by_name
565  * @ignore
566  */
567 Resource.defineRequest(Application.prototype, 'subscription_by_name', function (name) {
568   var collection = this.data.resources.subscriptions;
569   return {
570     method: 'get',
571     url: collection.url,
572     query: { name: name },
573     headers: {
574       'Authorization': this.authorization('get_by_name', collection),
575       'Accept': this.mediaType('subscriptions')
576     }
577   };
578 });
579 
580 /**
581  * Gets the members collection.
582  * @name members
583  * @ignore
584  */
585 Resource.defineRequest(Application.prototype, 'members', function (options) {
586   var collection = this.data.resources.members;
587   return {
588     method: 'get',
589     url: collection.url,
590     query: options,
591     headers: {
592       'Authorization': this.authorization('all', collection),
593       'Accept': this.mediaType('members')
594     }
595   };
596 });
597 
598 /**
599  * Gets a member by login.  Returns a collection with a single value: { login: member }.
600  * @name member_by_login
601  * @ignore
602  */
603 Resource.defineRequest(Application.prototype, 'member_by_login', function (name) {
604   var collection = this.data.resources.members;
605   return {
606     method: 'get',
607     url: collection.url,
608     query: { name: name },
609     headers: {
610       'Authorization': this.authorization('get_by_login', collection),
611       'Accept': this.mediaType('member')
612     }
613   };
614 });
615 
616 /**
617  * Creates a member.  Returns a member object.
618  * @name create_member
619  * @ignore
620  */
621 Resource.defineRequest(Application.prototype, 'create_member', function (data) {
622   var collection = this.data.resources.members;
623   return {
624     method: 'post',
625     url: collection.url,
626     content: data,
627     headers: {
628       'Authorization': this.authorization('create', collection),
629       'Accept': this.mediaType('member'),
630       'Content-Type': this.mediaType('member')
631     }
632   };
633 });
634 
635 /**
636  * Authenticates a member.  Returns a member object.
637  * @name authenticate_member
638  * @ignore
639  */
640 Resource.defineRequest(Application.prototype, 'authenticate_member', function (data) {
641   var collection = this.data.resources.authentication;
642   return {
643     method: 'post',
644     url: collection.url,
645     content: data,
646     headers: {
647       'Accept': this.mediaType('member')
648     }
649   };
650 });
651 
652 /**
653  * Requests a password reset for a member.  Returns a member object.
654  * @name authenticate_member
655  * @ignore
656  */
657 Resource.defineRequest(Application.prototype, 'reset_member_password', function (email) {
658   var collection = this.data.resources.authentication;
659   return {
660     method: 'post',
661     url: collection.url,
662     content: "",
663     query: { email: email }
664   };
665 });
666