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