Skip to content
This repository
Browse code

issue #19735: Update backbone to 0.9.10 and backbone realtional to 0.8.0

  • Loading branch information...
commit 40be4977474610ffc561499e09837c52de729824 1 parent 194df4b
John Rogelstad authored March 09, 2013
62  enyo-client/application/source/ext/datasource.js
@@ -19,7 +19,7 @@ white:true*/
19 19
     @param {Object} query
20 20
     @param {Object} options
21 21
     */
22  
-    fetch: function (options) {
  22
+    fetch: function (collection, options) {
23 23
       options = options ? _.clone(options) : {};
24 24
       var that = this,
25 25
         payload = {},
@@ -42,7 +42,7 @@ white:true*/
42 42
           // currently dealing with two different protocols for response formatting
43 43
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].fetch) : response.data;
44 44
           if (options && options.success) {
45  
-            options.success.call(that, dataHash);
  45
+            options.success.call(that, collection, dataHash, options);
46 46
           }
47 47
         };
48 48
 
@@ -96,7 +96,7 @@ white:true*/
96 96
     @param {Number} id
97 97
     @param {Object} options
98 98
     */
99  
-    retrieveRecord: function (recordType, id, options) {
  99
+    retrieveRecord: function (model, options) {
100 100
       var that = this,
101 101
         payload = {},
102 102
         complete = function (response) {
@@ -126,13 +126,13 @@ white:true*/
126 126
 
127 127
           // Handle success
128 128
           if (options && options.success) {
129  
-            options.success.call(that, dataHash);
  129
+            options.success.call(that, model, dataHash, options);
130 130
           }
131 131
         };
132 132
 
133 133
       payload.requestType = 'retrieveRecord';
134  
-      payload.recordType = recordType;
135  
-      payload.id = id;
  134
+      payload.recordType = model.recordType;
  135
+      payload.id = options.id || model.id;
136 136
       payload.databaseType = options.databaseType;
137 137
       payload.options = { context: options.context };
138 138
 
@@ -169,7 +169,7 @@ white:true*/
169 169
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].commit_record) : response.data;
170 170
           //dataHash = JSON.parse(response.data.rows[0].commit_record);
171 171
           if (options && options.success) {
172  
-            options.success.call(that, dataHash);
  172
+            options.success.call(that, model, dataHash, options);
173 173
           }
174 174
         };
175 175
 
@@ -222,7 +222,7 @@ white:true*/
222 222
           dataHash = response.data.rows ? JSON.parse(response.data.rows[0].dispatch) : response.data;
223 223
           //dataHash = JSON.parse(response.data.rows[0].dispatch);
224 224
           if (options && options.success) {
225  
-            options.success.call(that, dataHash);
  225
+            options.success.call(that, dataHash, options);
226 226
           }
227 227
         };
228 228
 
@@ -243,13 +243,13 @@ white:true*/
243 243
     */
244 244
     resetPassword: function (id, options) {
245 245
       var payload = {
246  
-            id: id
247  
-          },
248  
-          ajax = new enyo.Ajax({
249  
-            url: "/resetPassword",
250  
-            success: options ? options.success : undefined,
251  
-            error: options ? options.error : undefined
252  
-          });
  246
+          id: id
  247
+        },
  248
+        ajax = new enyo.Ajax({
  249
+          url: "/resetPassword",
  250
+          success: options ? options.success : undefined,
  251
+          error: options ? options.error : undefined
  252
+        });
253 253
 
254 254
       if (options.newUser) {
255 255
         // we don't want to send false at all, because false turns
@@ -269,14 +269,14 @@ white:true*/
269 269
     */
270 270
     changePassword: function (params, options) {
271 271
       var payload = {
272  
-            oldPassword: params.oldPassword,
273  
-            newPassword: params.newPassword
274  
-          },
275  
-          ajax = new enyo.Ajax({
276  
-            url: "/changePassword",
277  
-            success: options ? options.success : undefined,
278  
-            error: options ? options.error : undefined
279  
-          });
  272
+          oldPassword: params.oldPassword,
  273
+          newPassword: params.newPassword
  274
+        },
  275
+        ajax = new enyo.Ajax({
  276
+          url: "/changePassword",
  277
+          success: options ? options.success : undefined,
  278
+          error: options ? options.error : undefined
  279
+        });
280 280
 
281 281
       ajax.response(this.ajaxSuccess);
282 282
       ajax.go(payload);
@@ -313,10 +313,10 @@ white:true*/
313 313
     */
314 314
     sendEmail: function (payload, options) {
315 315
       var ajax = new enyo.Ajax({
316  
-            url: "/email",
317  
-            success: options ? options.success : undefined,
318  
-            error: options ? options.error : undefined
319  
-          });
  316
+          url: "/email",
  317
+          success: options ? options.success : undefined,
  318
+          error: options ? options.error : undefined
  319
+        });
320 320
 
321 321
       if (payload.body && !payload.text) {
322 322
         // be flexible with the inputs. Node-emailer prefers the term text, but
@@ -337,10 +337,10 @@ white:true*/
337 337
     */
338 338
     getExtensionList: function (options) {
339 339
       var ajax = new enyo.Ajax({
340  
-            url: "/extensions",
341  
-            success: options ? options.success : undefined,
342  
-            error: options ? options.error : undefined
343  
-          });
  340
+          url: "/extensions",
  341
+          success: options ? options.success : undefined,
  342
+          error: options ? options.error : undefined
  343
+        });
344 344
 
345 345
       ajax.response(this.ajaxSuccess);
346 346
       ajax.go();
2  lib/backbone-x/lib/Backbone-relational
... ...
@@ -1 +1 @@
1  
-Subproject commit b3dbb3c16ceb9f3f3dba96ac0587c7be9c6cc0f2
  1
+Subproject commit fb837d3f3ebd9f44e25b5f514e934e4870631a25
2  lib/backbone-x/lib/backbone
... ...
@@ -1 +1 @@
1  
-Subproject commit 863814e519e630806096aa3ddeef520afbb263ff
  1
+Subproject commit 836a8cb4d8744fcd71d6ca227dda051a4e5cb325
11  lib/backbone-x/source/collection.js
@@ -223,14 +223,7 @@ white:true*/
223 223
       //
224 224
       options = options ? _.clone(options) :
225 225
         this.orderAttribute ? { query: this.orderAttribute } : {};
226  
-      /*
227  
-      var that = this,
228  
-        success = options.success;
229  
-      options.success = function (resp) {
230  
-        XT.log("Successfully fetched:" + that.model.prototype.recordType, resp);
231  
-        if (success) { success(resp); }
232  
-      };
233  
-      */
  226
+
234 227
       return Backbone.Collection.prototype.fetch.call(this, options);
235 228
     },
236 229
 
@@ -244,7 +237,7 @@ white:true*/
244 237
       options.databaseType = model.model.prototype.databaseType;
245 238
 
246 239
       if (method === 'read' && options.query.recordType && options.success) {
247  
-        return XT.dataSource.fetch(options);
  240
+        return XT.dataSource.fetch(this, options);
248 241
       }
249 242
 
250 243
       return false;
19  lib/backbone-x/source/model.js
@@ -1118,7 +1118,7 @@ white:true*/
1118 1118
 
1119 1119
       // Read
1120 1120
       if (method === 'read' && recordType && id && options.success) {
1121  
-        result = dataSource.retrieveRecord(recordType, id, options);
  1121
+        result = dataSource.retrieveRecord(this, options);
1122 1122
 
1123 1123
       // Write
1124 1124
       } else if (method === 'create' || method === 'update' || method === 'delete') {
@@ -1515,18 +1515,20 @@ white:true*/
1515 1515
       Overload: Need to handle status here
1516 1516
     */
1517 1517
     findOrCreate: function (attributes, options) {
1518  
-      options = options ? _.clone(options) : {};
1519  
-     // Try to find an instance of 'this' model type in the store
1520  
-      var model = Backbone.Relational.store.find(this, attributes),
1521  
-        K = XM.Model;
  1518
+      options = options ? options : {};
  1519
+      var parsedAttributes = (_.isObject(attributes) && options.parse && this.prototype.parse) ?
  1520
+				this.prototype.parse(attributes) : attributes;
  1521
+
  1522
+			// Try to find an instance of 'this' model type in the store
  1523
+      var model = Backbone.Relational.store.find(this, parsedAttributes);
1522 1524
 
1523 1525
       // If we found an instance, update it with the data in 'item'; if not, create an instance
1524 1526
       // (unless 'options.create' is false).
1525 1527
       if (_.isObject(attributes)) {
1526  
-        if (model) {
1527  
-          model.setStatus(K.BUSY_FETCHING);
  1528
+        if (model && options.merge !== false) {
  1529
+          model.setStatus(XM.Model.BUSY_FETCHING);
1528 1530
           model.set(attributes, options);
1529  
-        } else if (options.create !== false) {
  1531
+        } else if (!model && options.create !== false) {
1530 1532
           model = this.build(attributes, options);
1531 1533
         }
1532 1534
       }
@@ -1748,5 +1750,4 @@ white:true*/
1748 1750
     if (options && options.isFetching) { this.status = XM.Model.BUSY_FETCHING; }
1749 1751
   };
1750 1752
 
1751  
-
1752 1753
 }());
21  node-datasource/lib/ext/datasource.js
@@ -14,7 +14,7 @@ white:true*/
14 14
     @param {Object} query
15 15
     @param {Object} options
16 16
     */
17  
-    fetch: function (options) {
  17
+    fetch: function (collection, options) {
18 18
       options = options ? _.clone(options) : {};
19 19
       var that = this,
20 20
         payload = {},
@@ -38,7 +38,12 @@ white:true*/
38 38
           // Handle success
39 39
           dataHash = JSON.parse(response.rows[0].fetch);
40 40
           if (options && options.success) {
41  
-            options.success.call(that, dataHash);
  41
+            if (collection) {
  42
+              options.success.call(that, collection, dataHash, options);
  43
+            // Support for legacy code
  44
+            } else {
  45
+              options.success.call(that, dataHash);
  46
+            }
42 47
           }
43 48
         };
44 49
 
@@ -91,7 +96,7 @@ white:true*/
91 96
     @param {Number} id
92 97
     @param {Object} options
93 98
     */
94  
-    retrieveRecord: function (recordType, id, options) {
  99
+    retrieveRecord: function (model, options) {
95 100
       var that = this,
96 101
         payload = {},
97 102
         conn = X.options.globalDatabase,
@@ -121,12 +126,12 @@ white:true*/
121 126
 
122 127
           // Handle success
123 128
           if (options && options.success) {
124  
-            options.success.call(that, dataHash);
  129
+            options.success.call(that, model, dataHash, options);
125 130
           }
126 131
         };
127 132
 
128  
-      payload.recordType = recordType;
129  
-      payload.id = id;
  133
+      payload.recordType = model.recordType;
  134
+      payload.id = options.id || model.id;
130 135
       payload.options = { context: options.context };
131 136
       payload.username = options.username;
132 137
       payload = JSON.stringify(payload);
@@ -163,7 +168,7 @@ white:true*/
163 168
           // Handle ok or complete hash response
164 169
           dataHash = JSON.parse(response.rows[0].commit_record);
165 170
           if (options && options.success) {
166  
-            options.success.call(that, dataHash);
  171
+            options.success.call(that, model, dataHash, options);
167 172
           }
168 173
         };
169 174
 
@@ -218,7 +223,7 @@ white:true*/
218 223
             } catch (err) {
219 224
               dataHash = response.rows[0].dispatch;
220 225
             }
221  
-            options.success.call(that, dataHash);
  226
+            options.success.call(that, dataHash, options);
222 227
           }
223 228
         };
224 229
       payload = JSON.stringify(payload);
8  node-datasource/lib/ext/session.js
@@ -206,11 +206,11 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
206 206
         // let the user of this dispatch be the global username of the user making the request
207 207
         options.username = this.get("details").id;
208 208
         if (payload.requestType === 'fetch') {
209  
-          XT.dataSource.fetch(options);
  209
+          XT.dataSource.fetch(null, options);
210 210
         } else if (payload.requestType === 'dispatch') {
211 211
           XT.dataSource.dispatch(payload.className, payload.functionName, payload.parameters, options);
212 212
         } else if (payload.requestType === 'retrieveRecord') {
213  
-          XT.dataSource.retrieveRecord(payload.recordType, payload.id, options);
  213
+          XT.dataSource.retrieveRecord(payload, options);
214 214
         } else if (payload.requestType === 'commitRecord') {
215 215
           if (!payload.dataHash) { return this.set("error", "invalid commit"); }
216 216
           // Passing payload through, but trick dataSource into thinking it's a Model:
@@ -303,7 +303,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
303 303
         recordType: "XM.Session",
304 304
         parameters: [
305 305
           { attribute: "id", value: this.get("id") || -1 },
306  
-          { attribute: "sid", value: this.get("sid") || -1 },
  306
+          { attribute: "sid", value: this.get("sid") || -1 }
307 307
 // TODO - This doesn't work.
308 308
 // PostgreSQL error: Error: operator does not exist: bigint = date
309 309
           //{ attribute: "created", value: this.get("created") || -1 }
@@ -325,7 +325,7 @@ regexp:true, undef:true, strict:true, trailing:true, white:true */
325 325
         callback(err);
326 326
       };
327 327
 
328  
-      XT.dataSource.fetch(validateOptions);
  328
+      XT.dataSource.fetch(null, validateOptions);
329 329
       return this;
330 330
     },
331 331
 
948  node-datasource/oauth2/db/connect-xt-pg.js
... ...
@@ -1,474 +1,476 @@
1  
-/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2  
-regexp:true, undef:true, strict:true, trailing:true, white:true */
3  
-/*global X:true, XM:true, XT:true, console:true, _:true*/
4  
-
5  
-/*!
6  
- * Connect - xTuple - PostgreSQL Store
7  
- *
8  
- * connect-xt-pg is a PostgreSQL session store that uses xTuple's glabal database
9  
- * to persist session data.  To speed up session reads, Express's Session MemoryStore
10  
- * is used as a cache.
11  
- *
12  
- * Modeled off of:
13  
- * - https://github.com/visionmedia/connect-redis/blob/master/lib/connect-redis.js
14  
- * - https://github.com/kcbanner/connect-mongo/blob/master/lib/connect-mongo.js
15  
- * - https://github.com/jebas/connect-pg/blob/master/lib/connect-pg.js
16  
- */
17  
-
18  
-
19  
-/**
20  
- * Return the `XTPGStore` extending `connect`'s session Store.
21  
- *
22  
- * @param {object} connect
23  
- * @return {Function}
24  
- * @api public
25  
- */
26  
-
27  
-module.exports = function (connect) {
28  
-  "use strict";
29  
-
30  
-  /**
31  
-   * Connect's Store.
32  
-   */
33  
-  var Store = connect.session.Store,
34  
-      // TODO - If we ever run multiple processes/servers, MemoryStore must be replaced with something
35  
-      // all processes/servers can use/share and keep in sync like Redis.
36  
-      MemoryStore = new connect.session.MemoryStore();
37  
-
38  
-  /**
39  
-   * Initialize XTPGStore with the given `options`.
40  
-   *
41  
-   * @param {Object} options
42  
-   * @api public
43  
-   */
44  
-  function XTPGStore(options) {
45  
-    var self = this;
46  
-
47  
-    options = options || {};
48  
-    Store.call(this, options);
49  
-    this.prefix = null == options.prefix ? 'sess:' : options.prefix;
50  
-    this.hybridCache = null == options.hybridCache ? false : options.hybridCache;
51  
-    this.sessions = {};
52  
-
53  
-    // Load all the data from XM.SessionStore into the Express MemoryStore for caching.
54  
-    this.loadSessions = function (options, callback) {
55  
-      // TODO - options could be used to only load parital dataset of recently active sessions.
56  
-      // It could also be used to help process/server syncing if we move to something like Redis.
57  
-
58  
-      var fetchOptions = {};
59  
-
60  
-      fetchOptions.success = function (sessionstore) {
61  
-        var sid,
62  
-            sess;
63  
-
64  
-        // Flush any sessions before reloading it.
65  
-        self.sessions = {};
66  
-
67  
-        _.each(sessionstore, function (model, id, collection) {
68  
-          sid = model.id;
69  
-          sess = JSON.parse(model.session);
70  
-
71  
-          if (self.hybridCache) {
72  
-            // Store the session data in the Express MemeoryStore for caching.
73  
-            MemoryStore.set(sid, sess, function () {});
74  
-          }
75  
-
76  
-          self.sessions[sid] = sess;
77  
-        });
78  
-
79  
-        // Now that sessions are loaded, we'll call the callback that was waiting for them.
80  
-        if (callback &&  typeof callback === 'function') {
81  
-          callback();
82  
-        }
83  
-      };
84  
-      fetchOptions.error = function (sessionstore, err) {
85  
-        X.debug("Session Collection fetch failed.");
86  
-
87  
-        // Now that sessions are loaded, we'll call the callback that was waiting for them.
88  
-        if (callback &&  typeof callback === 'function') {
89  
-          callback("Session Collection fetch failed.");
90  
-        }
91  
-      };
92  
-
93  
-      // TODO - This is REALLY SLOW if there are 10,000 sessions in the table and we use collection.fetch().
94  
-      // var sessionsCollection = new XM.SessionStoreCollection();
95  
-      //sessionsCollection.fetch(fetchOptions);
96  
-
97  
-      // Fetch all records from XM.SessionStore and load them into the Express MemoryStore.
98  
-      fetchOptions.query = {
99  
-        requestType: "fetch",
100  
-        recordType: "XM.SessionStore"
101  
-      };
102  
-
103  
-      // fetchOptions.username = GLOBAL_USERNAME; // TODO
104  
-      fetchOptions.username = 'node';
105  
-      XT.dataSource.fetch(fetchOptions);
106  
-    };
107  
-
108  
-    // Loops through the sessions, find the ones that are expired and sends that session data
109  
-    // to the callback function. This allows us to expire session from code that has access to
110  
-    // stuff like socket.io in main.js.
111  
-    this.expireSessions = function (callback) {
112  
-      this.loadSessions(null, function (err) {
113  
-        if (err) {
114  
-          return;
115  
-        }
116  
-
117  
-        _.each(self.sessions, function (val, key, list) {
118  
-          var expires = new Date(val.cookie.expires),
119  
-              now = new Date();
120  
-
121  
-          if ((expires - now) <= 0) {
122  
-            //X.debug("Session: ", key, " expired ", (expires - now));
123  
-            callback(key, val);
124  
-          } else {
125  
-            //X.debug("Session: ", key, " expires in ", (expires - now));
126  
-          }
127  
-        });
128  
-      });
129  
-    };
130  
-
131  
-    X.debug("XTPGStore SessionStore using hybridCache = ", this.hybridCache);
132  
-    // Prime this.sessions and MemoryCache on initialization.
133  
-    this.loadSessions();
134  
-  }
135  
-
136  
-  /**
137  
-   * Inherit from `Store`.
138  
-   */
139  
-  XTPGStore.prototype = new Store();
140  
-
141  
-  /**
142  
-   * Attempt to fetch session by the given `sid`.
143  
-   *
144  
-   * @param {String} sid
145  
-   * @param {Function} fn
146  
-   * @api public
147  
-   */
148  
-  XTPGStore.prototype.get = function (sid, done) {
149  
-    try {
150  
-      var fetchCache,
151  
-          fetchDB,
152  
-          that = this;
153  
-
154  
-      sid = this.prefix + sid;
155  
-
156  
-      fetchDB = function () {
157  
-        var fetchOptions = {},
158  
-            result,
159  
-            sessionStore = {};
160  
-
161  
-        sessionStore = new XM.SessionStore();
162  
-        fetchOptions.id = sid;
163  
-
164  
-        fetchOptions.success = function (model) {
165  
-          // We have a matching session store cookie
166  
-
167  
-          // TODO - update lastModified time to extend timeout?
168  
-          // Doing this will complicate things with a hybridCache MemoryStore.
169  
-          // Do we really need to set that here for just in XM.Session and have CleanupTask
170  
-          // clean up XM.SessionStore on timeouts?
171  
-          //model.set("lastModified", new Date().getTime());
172  
-          //model.save(null, saveOptions);
173  
-
174  
-          if (model.get("session")) {
175  
-            result = JSON.parse(model.get("session"));
176  
-          }
177  
-
178  
-          if (that.hybridCache) {
179  
-            // We fetched from the database because this session was not in the MemoryStore.
180  
-            // Load this data into the MemoryStore to speed this up next time.
181  
-            MemoryStore.set(sid, result, function (err, cache) {
182  
-              if (err) {
183  
-                // Could not set. This shouldn't happen. Return error and move along...
184  
-                console.trace("MemoryStore.set error:");
185  
-                done && done(err);
186  
-              } else {
187  
-                // Return the session data back to Express to be used by routes and functors.
188  
-                return done(null, result);
189  
-              }
190  
-            });
191  
-          } else {
192  
-            // Return the session data back to Express to be used by routes and functors.
193  
-            return done(null, result);
194  
-          }
195  
-        };
196  
-        fetchOptions.error = function (model, err) {
197  
-          // Session was not found. This can happen if cookie is still in the browser, but
198  
-          // db record was removed by CleanupTask because it has timed out.
199  
-
200  
-          // This is called when MemoryStore did not find session data.
201  
-          // No need to touch MemoryStore here.
202  
-
203  
-          // Nothing found anywhere, no need to return the err, return nothing and move along...
204  
-          done && done();
205  
-        };
206  
-
207  
-        // Try to fetch a session matching the user's cookie sid.
208  
-        sessionStore.fetch(fetchOptions);
209  
-      };
210  
-
211  
-      if (this.hybridCache) {
212  
-        // Try to get session data from MemoryStore.
213  
-        fetchCache = function (err, cache) {
214  
-          if (cache && !err) {
215  
-            return done(null, cache);
216  
-          } else {
217  
-            fetchDB();
218  
-          }
219  
-        };
220  
-        MemoryStore.get(sid, fetchCache);
221  
-      } else {
222  
-        fetchDB();
223  
-      }
224  
-    } catch (err) {
225  
-      done && done(err);
226  
-    }
227  
-  };
228  
-
229  
-  /**
230  
-   * Commit the given `sess` object associated with the given `sid`.
231  
-   *
232  
-   * @param {String} sid
233  
-   * @param {Session} sess
234  
-   * @param {Function} fn
235  
-   * @api public
236  
-   */
237  
-  XTPGStore.prototype.set = function (sid, sess, done) {
238  
-    try {
239  
-      var fetchCache,
240  
-          fetchDB,
241  
-          that = this;
242  
-
243  
-      sid = this.prefix + sid;
244  
-      sess = JSON.stringify(sess);
245  
-
246  
-      fetchDB = function () {
247  
-        var fetchOptions = {},
248  
-            saveOptions = {},
249  
-            sessionAttributes = {},
250  
-            sessionStore = {};
251  
-
252  
-        sessionStore = new XM.SessionStore();
253  
-
254  
-        saveOptions.success = function (model) {
255  
-          if (that.hybridCache) {
256  
-            // Overwrite the MemoryStore with the new data, replacing old or creating new entry.
257  
-            MemoryStore.set(sid, JSON.parse(sess), function (err, cache) {
258  
-              if (err) {
259  
-                // Could not set. Return error and move along.
260  
-                console.trace("MemoryStore.set error:");
261  
-                done && done(err);
262  
-              } else {
263  
-                // Success, MemoryStore updated, move along.
264  
-                done && done();
265  
-              }
266  
-            });
267  
-          } else {
268  
-            // Success, move along.
269  
-            done && done();
270  
-          }
271  
-        };
272  
-        saveOptions.error = function (model, err) {
273  
-          var that = this;
274  
-          // This shouldn't happen. How did we get here? Log trace.
275  
-          console.trace("XM.SessionStore save error. This shouldn't happen.");
276  
-
277  
-          if (that.hybridCache) {
278  
-            // Delete any match in MemoryStore.
279  
-            MemoryStore.destroy(sid, function (err, cache) {
280  
-              if (err) {
281  
-                // Could not destroy. This shouldn't happen. Return error and move along.
282  
-                console.trace("MemoryStore.destroy error:");
283  
-                done && done(err);
284  
-              } else {
285  
-                // TODO - This might throw an error because our err object does not includes a stack.
286  
-                // https://github.com/senchalabs/connect/blob/master/lib/middleware/errorHandler.js#L48
287  
-                // MemoryStore destroyed, move along.
288  
-                done && done(that.err);
289  
-              }
290  
-            });
291  
-          } else {
292  
-            // TODO - This might throw an error because our err object does not includes a stack.
293  
-            // https://github.com/senchalabs/connect/blob/master/lib/middleware/errorHandler.js#L48
294  
-            // Return nothing and move along.
295  
-            done && done(err);
296  
-          }
297  
-        };
298  
-
299  
-        fetchOptions.id = sid;
300  
-
301  
-        fetchOptions.success = function (model) {
302  
-          // Fetch found this session, update it and save.
303  
-          model.set("session", sess);
304  
-
305  
-          // Set gets called a lot. There isn't always a change to save and save will fail.
306  
-          // Check if this model has changed before trying to save it.
307  
-          if (model.getStatusString() === "READY_CLEAN") {
308  
-            if (that.hybridCache) {
309  
-              // MemoryStore did not have a matching session, update existing or add new.
310  
-              MemoryStore.set(sid, JSON.parse(sess), function (err, cache) {
311  
-                if (err) {
312  
-                  // Could not set. This shouldn't happen. Return error and move along.
313  
-                  console.trace("MemoryStore.set error:");
314  
-                  done && done(err);
315  
-                } else {
316  
-                  // Nothing to save, MemoryStore updated, move along.
317  
-                  done && done();
318  
-                }
319  
-              });
320  
-            } else {
321  
-              // Nothing to save, move along.
322  
-              done && done();
323  
-            }
324  
-          } else if (model.getStatusString() === "READY_DIRTY") {
325  
-            // Try to save XM.SessionStore to database.
326  
-            model.save(null, saveOptions);
327  
-          }
328  
-        };
329  
-        fetchOptions.error = function (model, err) {
330  
-          // Fetch did not find this session, initialize new and save.
331  
-          // Create new XM.SessionStore object.
332  
-          sessionStore = new XM.SessionStore();
333  
-          // TODO - Is this redundant?  Can I just call model.initialize...???
334  
-          sessionStore.initialize(null, {isNew: true});
335  
-
336  
-          sessionAttributes = {
337  
-            id: sid,
338  
-            session: sess
339  
-          };
340  
-
341  
-          // Try to save XM.SessionStore to database.
342  
-          sessionStore.save(sessionAttributes, saveOptions);
343  
-        };
344  
-
345  
-        // Try to fetch a session matching the user's cookie sid to
346  
-        // see if we need to make a new one or update the old one.
347  
-        sessionStore.fetch(fetchOptions);
348  
-      };
349  
-
350  
-      if (this.hybridCache) {
351  
-        // Try to get session data from MemoryStore.
352  
-        fetchCache = function (err, cache) {
353  
-          if (cache && !err) {
354  
-            if (JSON.stringify(cache, null, null) === sess) {
355  
-              // No change to the session data, nothing to save, move along.
356  
-              done && done();
357  
-            } else {
358  
-              fetchDB();
359  
-            }
360  
-          } else {
361  
-            fetchDB();
362  
-          }
363  
-        };
364  
-        MemoryStore.get(sid, fetchCache);
365  
-      } else {
366  
-        fetchDB();
367  
-      }
368  
-    } catch (err) {
369  
-      done && done(err);
370  
-    }
371  
-  };
372  
-
373  
-  /**
374  
-   * Destroy the session associated with the given `sid`.
375  
-   *
376  
-   * @param {String} sid
377  
-   * @api public
378  
-   */
379  
-  XTPGStore.prototype.destroy = function (sid, done) {
380  
-    try {
381  
-      var sessionStore = {},
382  
-          fetchOptions = {},
383  
-          that = this;
384  
-
385  
-      sid = this.prefix + sid;
386  
-      sessionStore = new XM.SessionStore();
387  
-      fetchOptions.id = sid;
388  
-
389  
-      fetchOptions.success = function (model) {
390  
-        // Delete this session from the db store.
391  
-        model.destroy();
392  
-
393  
-        if (that.hybridCache) {
394  
-          // Delete this session from the MemoryStore as well.
395  
-          MemoryStore.destroy(sid, function (err, cache) {
396  
-            if (err) {
397  
-              // Could not destroy. This shouldn't happen. Return error and move along.
398  
-              console.trace("MemoryStore.destroy error:");
399  
-              done && done(err);
400  
-            } else {
401  
-              // MemoryStore destroyed, move along.
402  
-              done && done();
403  
-            }
404  
-          });
405  
-        } else {
406  
-          done && done();
407  
-        }
408  
-      };
409  
-      fetchOptions.error = function (model, err) {
410  
-        if (that.hybridCache) {
411  
-          // Did not find a match in the db, delete this session from the MemoryStore, it's invalid.
412  
-          MemoryStore.destroy(sid, function (err, cache) {
413  
-            if (err) {
414  
-              // Could not destroy. This shouldn't happen. Return error and move along.
415  
-              console.trace("MemoryStore.destroy error:");
416  
-              done && done(err);
417  
-            } else {
418  
-              // MemoryStore destroyed. Return nothing and move along.
419  
-              done && done();
420  
-            }
421  
-          });
422  
-        } else {
423  
-          // Return nothing and move along.
424  
-          done && done();
425  
-        }
426  
-      };
427  
-
428  
-      // Try to fetch a session matching the user's cookie sid.
429  
-      sessionStore.fetch(fetchOptions);
430  
-    } catch (err) {
431  
-      done && done(err);
432  
-    }
433  
-  };
434  
-
435  
-  /**
436  
-   * Invoke the given callback `fn` with all active sessions.
437  
-   *
438  
-   * @param {Function} fn
439  
-   * @api public
440  
-   */
441  
-  XTPGStore.prototype.all = function (fn) {
442  
-    this.loadSessions(null, function (err) {
443  
-      if (err) {
444  
-        return fn(err);
445  
-      }
446  
-
447  
-      var arr = [],
448  
-          keys = Object.keys(this.sessions);
449  
-
450  
-      for (var i = 0, len = keys.length; i < len; ++i) {
451  
-        arr.push(this.sessions[keys[i]]);
452  
-      }
453  
-      fn(null, arr);
454  
-    });
455  
-  };
456  
-
457  
-  /**
458  
-   * Fetch number of sessions.
459  
-   *
460  
-   * @param {Function} fn
461  
-   * @api public
462  
-   */
463  
-  XTPGStore.prototype.length = function (fn) {
464  
-    this.loadSessions(null, function (err) {
465  
-      if (err) {
466  
-        return fn(err);
467  
-      }
468  
-
469  
-      fn(null, Object.keys(this.sessions).length);
470  
-    });
471  
-  };
472  
-
473  
-  return XTPGStore;
  1
+/*jshint node:true, indent:2, curly:false, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
  2
+regexp:true, undef:true, strict:true, trailing:true, white:true */
  3
+/*global X:true, XM:true, XT:true, console:true, _:true*/
  4
+
  5
+/*!
  6
+ * Connect - xTuple - PostgreSQL Store
  7
+ *
  8
+ * connect-xt-pg is a PostgreSQL session store that uses xTuple's glabal database
  9
+ * to persist session data.  To speed up session reads, Express's Session MemoryStore
  10
+ * is used as a cache.
  11
+ *
  12
+ * Modeled off of:
  13
+ * - https://github.com/visionmedia/connect-redis/blob/master/lib/connect-redis.js
  14
+ * - https://github.com/kcbanner/connect-mongo/blob/master/lib/connect-mongo.js
  15
+ * - https://github.com/jebas/connect-pg/blob/master/lib/connect-pg.js
  16
+ */
  17
+
  18
+
  19
+/**
  20
+ * Return the `XTPGStore` extending `connect`'s session Store.
  21
+ *
  22
+ * @param {object} connect
  23
+ * @return {Function}
  24
+ * @api public
  25
+ */
  26
+
  27
+module.exports = function (connect) {
  28
+  "use strict";
  29
+
  30
+  /**
  31
+   * Connect's Store.
  32
+   */
  33
+  var Store = connect.session.Store,
  34
+      // TODO - If we ever run multiple processes/servers, MemoryStore must be replaced with something
  35
+      // all processes/servers can use/share and keep in sync like Redis.
  36
+      MemoryStore = new connect.session.MemoryStore();
  37
+
  38
+  /**
  39
+   * Initialize XTPGStore with the given `options`.
  40
+   *
  41
+   * @param {Object} options
  42
+   * @api public
  43
+   */
  44
+  function XTPGStore(options) {
  45
+    var self = this;
  46
+
  47
+    options = options || {};
  48
+    Store.call(this, options);
  49
+    this.prefix = null === options.prefix ? 'sess:' : options.prefix;
  50
+    this.hybridCache = null === options.hybridCache ? false : options.hybridCache;
  51
+    this.sessions = {};
  52
+
  53
+    // Load all the data from XM.SessionStore into the Express MemoryStore for caching.
  54
+    this.loadSessions = function (options, callback) {
  55
+      // TODO - options could be used to only load parital dataset of recently active sessions.
  56
+      // It could also be used to help process/server syncing if we move to something like Redis.
  57
+
  58
+      var fetchOptions = {};
  59
+
  60
+      fetchOptions.success = function (sessionstore) {
  61
+        var sid,
  62
+            sess;
  63
+
  64
+        // Flush any sessions before reloading it.
  65
+        self.sessions = {};
  66
+
  67
+        _.each(sessionstore, function (model, id, collection) {
  68
+          sid = model.id;
  69
+          sess = JSON.parse(model.session);
  70
+
  71
+          if (self.hybridCache) {
  72
+            // Store the session data in the Express MemeoryStore for caching.
  73
+            MemoryStore.set(sid, sess, function () {});
  74
+          }
  75
+
  76
+          self.sessions[sid] = sess;
  77
+        });
  78
+
  79
+        // Now that sessions are loaded, we'll call the callback that was waiting for them.
  80
+        if (callback &&  typeof callback === 'function') {
  81
+          callback();
  82
+        }
  83
+      };
  84
+      fetchOptions.error = function (sessionstore, err) {
  85
+        X.debug("Session Collection fetch failed.");
  86
+
  87
+        // Now that sessions are loaded, we'll call the callback that was waiting for them.
  88
+        if (callback &&  typeof callback === 'function') {
  89
+          callback("Session Collection fetch failed.");
  90
+        }
  91
+      };
  92
+
  93
+      // TODO - This is REALLY SLOW if there are 10,000 sessions in the table and we use collection.fetch().
  94
+      // var sessionsCollection = new XM.SessionStoreCollection();
  95
+      //sessionsCollection.fetch(fetchOptions);
  96
+
  97
+      // Fetch all records from XM.SessionStore and load them into the Express MemoryStore.
  98
+      fetchOptions.query = {
  99
+        requestType: "fetch",
  100
+        recordType: "XM.SessionStore"
  101
+      };
  102
+
  103
+      // fetchOptions.username = GLOBAL_USERNAME; // TODO
  104
+      fetchOptions.username = 'node';
  105
+      XT.dataSource.fetch(null, fetchOptions);
  106
+    };
  107
+
  108
+    // Loops through the sessions, find the ones that are expired and sends that session data
  109
+    // to the callback function. This allows us to expire session from code that has access to
  110
+    // stuff like socket.io in main.js.
  111
+    this.expireSessions = function (callback) {
  112
+      this.loadSessions(null, function (err) {
  113
+        if (err) {
  114
+          return;
  115
+        }
  116
+
  117
+        _.each(self.sessions, function (val, key, list) {
  118
+          var expires = new Date(val.cookie.expires),
  119
+              now = new Date();
  120
+
  121
+          if ((expires - now) <= 0) {
  122
+            //X.debug("Session: ", key, " expired ", (expires - now));
  123
+            callback(key, val);
  124
+          } else {
  125
+            //X.debug("Session: ", key, " expires in ", (expires - now));
  126
+          }
  127
+        });
  128
+      });
  129
+    };
  130
+
  131
+    X.debug("XTPGStore SessionStore using hybridCache = ", this.hybridCache);
  132
+    // Prime this.sessions and MemoryCache on initialization.
  133
+    this.loadSessions();
  134
+  }
  135
+
  136
+  /**
  137
+   * Inherit from `Store`.
  138
+   */
  139
+  XTPGStore.prototype = new Store();
  140
+
  141
+  /**
  142
+   * Attempt to fetch session by the given `sid`.
  143
+   *
  144
+   * @param {String} sid
  145
+   * @param {Function} fn
  146
+   * @api public
  147
+   */
  148
+  XTPGStore.prototype.get = function (sid, done) {
  149
+    try {
  150
+      var fetchCache,
  151
+          fetchDB,
  152
+          that = this;
  153
+
  154
+      sid = this.prefix + sid;
  155
+
  156
+      fetchDB = function () {
  157
+        var fetchOptions = {},
  158
+            result,
  159
+            sessionStore = {};
  160
+
  161
+        sessionStore = new XM.SessionStore();
  162
+        fetchOptions.id = sid;
  163
+
  164
+        fetchOptions.success = function (model) {
  165
+          // We have a matching session store cookie
  166
+
  167
+          // TODO - update lastModified time to extend timeout?
  168
+          // Doing this will complicate things with a hybridCache MemoryStore.
  169
+          // Do we really need to set that here for just in XM.Session and have CleanupTask
  170
+          // clean up XM.SessionStore on timeouts?
  171
+          //model.set("lastModified", new Date().getTime());
  172
+          //model.save(null, saveOptions);
  173
+
  174
+          if (model.get("session")) {
  175
+            result = JSON.parse(model.get("session"));
  176
+          }
  177
+
  178
+          if (that.hybridCache) {
  179
+            // We fetched from the database because this session was not in the MemoryStore.
  180
+            // Load this data into the MemoryStore to speed this up next time.
  181
+            MemoryStore.set(sid, result, function (err, cache) {
  182
+              if (err) {
  183
+                // Could not set. This shouldn't happen. Return error and move along...
  184
+                console.trace("MemoryStore.set error:");
  185
+                done && done(err);
  186
+              } else {
  187
+                // Return the session data back to Express to be used by routes and functors.
  188
+                return done(null, result);
  189
+              }
  190
+            });
  191
+          } else {
  192
+            // Return the session data back to Express to be used by routes and functors.
  193
+            return done(null, result);
  194
+          }
  195
+        };
  196
+        fetchOptions.error = function (model, err) {
  197
+          // Session was not found. This can happen if cookie is still in the browser, but
  198
+          // db record was removed by CleanupTask because it has timed out.
  199
+
  200
+          // This is called when MemoryStore did not find session data.
  201
+          // No need to touch MemoryStore here.
  202
+
  203
+          // Nothing found anywhere, no need to return the err, return nothing and move along...
  204
+          done && done();
  205
+        };
  206
+
  207
+        // Try to fetch a session matching the user's cookie sid.
  208
+        sessionStore.fetch(fetchOptions);
  209
+      };
  210
+
  211
+      if (this.hybridCache) {
  212
+        // Try to get session data from MemoryStore.
  213
+        fetchCache = function (err, cache) {
  214
+          if (cache && !err) {
  215
+            return done(null, cache);
  216
+          } else {
  217
+            fetchDB();
  218
+          }
  219
+        };
  220
+        MemoryStore.get(sid, fetchCache);
  221
+      } else {
  222
+        fetchDB();
  223
+      }
  224
+    } catch (err) {
  225
+      done && done(err);
  226
+    }
  227
+  };
  228
+
  229
+  /**
  230
+   * Commit the given `sess` object associated with the given `sid`.
  231
+   *
  232
+   * @param {String} sid
  233
+   * @param {Session} sess
  234
+   * @param {Function} fn
  235
+   * @api public
  236
+   */
  237
+  XTPGStore.prototype.set = function (sid, sess, done) {
  238
+    try {
  239
+      var fetchCache,
  240
+          fetchDB,
  241
+          that = this,
  242
+          K = XM.Model;
  243
+
  244
+      sid = this.prefix + sid;
  245
+      sess = JSON.stringify(sess);
  246
+
  247
+      fetchDB = function () {
  248
+        var fetchOptions = {},
  249
+            saveOptions = {},
  250
+            sessionAttributes = {},
  251
+            sessionStore = {};
  252
+
  253
+        sessionStore = new XM.SessionStore();
  254
+
  255
+        saveOptions.success = function (model) {
  256
+          if (that.hybridCache) {
  257
+            // Overwrite the MemoryStore with the new data, replacing old or creating new entry.
  258
+            MemoryStore.set(sid, JSON.parse(sess), function (err, cache) {
  259
+              if (err) {
  260
+                // Could not set. Return error and move along.
  261
+                console.trace("MemoryStore.set error:");
  262
+                done && done(err);
  263
+              } else {
  264
+                // Success, MemoryStore updated, move along.
  265
+                done && done();
  266
+              }
  267
+            });
  268
+          } else {
  269
+            // Success, move along.
  270
+            done && done();
  271
+          }
  272
+        };
  273
+        saveOptions.error = function (model, err) {
  274
+          var that = this;
  275
+          // This shouldn't happen. How did we get here? Log trace.
  276
+          console.trace("XM.SessionStore save error. This shouldn't happen.");
  277
+
  278
+          if (that.hybridCache) {
  279
+            // Delete any match in MemoryStore.
  280
+            MemoryStore.destroy(sid, function (err, cache) {
  281
+              if (err) {
  282
+                // Could not destroy. This shouldn't happen. Return error and move along.
  283
+                console.trace("MemoryStore.destroy error:");
  284
+                done && done(err);
  285
+              } else {
  286
+                // TODO - This might throw an error because our err object does not includes a stack.
  287
+                // https://github.com/senchalabs/connect/blob/master/lib/middleware/errorHandler.js#L48
  288
+                // MemoryStore destroyed, move along.
  289
+                done && done(that.err);
  290
+              }
  291
+            });
  292
+          } else {
  293
+            // TODO - This might throw an error because our err object does not includes a stack.
  294
+            // https://github.com/senchalabs/connect/blob/master/lib/middleware/errorHandler.js#L48
  295
+            // Return nothing and move along.
  296
+            done && done(err);
  297
+          }
  298
+        };
  299
+
  300
+        fetchOptions.id = sid;
  301
+
  302
+        fetchOptions.success = function (model, resp) {
  303
+          var status = model.getStatus();
  304
+          // Fetch found this session, update it and save.
  305
+          model.set("session", sess);
  306
+
  307
+          // Set gets called a lot. There isn't always a change to save and save will fail.
  308
+          // Check if this model has changed before trying to save it.
  309
+          if (status === K.READY_CLEAN) {
  310
+            if (that.hybridCache) {
  311
+              // MemoryStore did not have a matching session, update existing or add new.
  312
+              MemoryStore.set(sid, JSON.parse(sess), function (err, cache) {
  313