support user-requested filtering of extensions. resolves #1844
[xtuple] / node-datasource / routes / app.js
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, SYS:true, XT:true, _:true */
4
5 var async = require("async"),
6   path = require("path"),
7   routes = require("./routes");
8
9 (function () {
10   "use strict";
11
12   //
13   // Get the most recent version of the core code
14   // XXX: we'll need to sometimes give older versions
15   // @param {String} language Can be "js" or "css".
16   //
17   var getCoreUuid = function (language, organization, callback) {
18     var coll = new SYS.ClientCodeRelationCollection();
19     coll.fetch({
20       username: X.options.databaseServer.user,
21       database: organization,
22       query: {
23         parameters: [
24           { attribute: "language", value: language },
25           { attribute: "extension", value: null, includeNull: true }
26         ]
27       },
28       success: function (coll, res) {
29         var sortedModels = _.sortBy(coll.models, function (model) {
30           return -1 * getVersionSize(model.get("version"));
31         });
32         callback(null, sortedModels[0].get("uuid"));
33       }
34     });
35   };
36
37   /**
38     Just get a sense of how recent a version is without the dots.
39     Higher version number string inputs will result in higher int outputs.
40     Works with three or four dot-separated numbers.
41   */
42   var getVersionSize = function (version) {
43     var versionSplit = version.split('.'),                  // e.g. "4.5.0-beta2".
44       versionSize = 1000000 * versionSplit[0] +             // Get "4" from "4.5.0-beta2".
45         10000 * versionSplit[1] +                           // Get "5" from "4.5.0-beta2".
46         100 * versionSplit[2].match(/^[0-9]+/g, '')[0],     // Get "0" from "0-beta2".
47       prerelease = versionSplit[2].replace(/^[0-9]+/g, ''), // Get "-beta2" from "0-beta2".
48       preRegEx = /([a-zA-Z]+)([0-9]*)/g,                    // Capture pre-release as ["beta2", "beta", "2"].
49       preMatch = preRegEx.exec(prerelease),
50       preVersion,
51       preNum;
52
53     if (versionSplit.length > 3) {
54       versionSize += versionSplit[3];
55     }
56
57     if (preMatch && preMatch.length && preMatch[0] !== '') {
58       if (preMatch[1] !== '') {
59         preVersion = preMatch[1].match(/[a-zA-Z]+/g);       // Get ["beta"] from ["beta2", "beta", "2"].
60
61         // Decrease versionSize for pre-releasees.
62         switch (preVersion[0].toLowerCase()) {
63           case 'alpha':
64             versionSize = versionSize - 50;
65             break;
66           case 'beta':
67             versionSize = versionSize - 40;
68             break;
69           case 'rc':
70             versionSize = versionSize - 20;
71             break;
72           default :
73             X.err("Cannot get pre-release version number.");
74         }
75       }
76
77       // Add pre-release version to versionSize.
78       if (preMatch[2] !== '') {
79         preNum = preMatch[2].match(/[0-9]+/g);              // Get ["2"] from ["beta2", "beta", "2"].
80         versionSize = versionSize + parseInt(preNum);
81       }
82     }
83
84     return versionSize;
85   };
86
87
88   /**
89    Figures out what extensions the user is entitled to. Queries to determine
90    client code UUIDs for core and those extensions. Sends on to the app view
91    to render.
92    */
93   var serveApp = exports.serveApp = function (req, res) {
94     if (!req.session.passport.user) {
95       routes.logout(req, res);
96       return;
97     }
98
99     var user = new SYS.User(),
100       fetchError = function (err) {
101         X.log("Extension fetch error", err);
102         res.send({isError: true, message: "Error fetching extensions"});
103       },
104       fetchSuccess = function (model, result) {
105         var sendExtensions = function (res, extensions) {
106           var filteredExtensions;
107           if (req.query.extensions) {
108             // the user is requesting to only see a certain set of extensions
109             filteredExtensions = JSON.parse(req.query.extensions);
110             extensions = extensions.filter(function (ext) {
111               return _.contains(filteredExtensions, ext.name);
112             });
113           }
114
115           extensions.sort(function (ext1, ext2) {
116             if (ext1.loadOrder !== ext2.loadOrder) {
117               return ext1.loadOrder - ext2.loadOrder;
118             } else {
119               return ext1.name > ext2.name ? 1 : -1;
120             }
121           });
122           var getLatestUuid = function (extensions, language) {
123             var uuids = _.map(extensions, function (ext) {
124               var jsModels = _.filter(ext.codeInfo, function (codeInfo) {
125                 return codeInfo.language === language;
126               });
127               var sortedModels = _.sortBy(jsModels, function (codeInfo) {
128                 return -1 * getVersionSize(codeInfo.version);
129               });
130               if (sortedModels[0]) {
131                 return sortedModels[0].uuid;
132               } else {
133                 X.log("Could not find js uuid for extension " + ext.description);
134                 return null;
135               }
136             });
137             return _.compact(uuids); // eliminate any null values
138           };
139           var extJsUuids = getLatestUuid(extensions, "js");
140           var extCssUuids = getLatestUuid(extensions, "css");
141           var extensionPaths = _.compact(_.map(extensions, function (ext) {
142             var locationName = ext.location.indexOf("/") === 0 ?
143               path.join(ext.location, "source") :
144               "/" + ext.location;
145             return path.join(locationName, ext.name);
146           }));
147           getCoreUuid('js', req.session.passport.user.organization, function (err, jsUuid) {
148             if (err) {
149               res.send({isError: true, error: err});
150               return;
151             }
152             getCoreUuid('css', req.session.passport.user.organization, function (err, cssUuid) {
153               if (err) {
154                 res.send({isError: true, error: err});
155                 return;
156               }
157               res.render(req.viewName || 'app', {
158                 org: req.session.passport.user.organization,
159                 coreJs: jsUuid,
160                 coreCss: cssUuid,
161                 extensionJsArray: extJsUuids,
162                 extensionCssArray: extCssUuids,
163                 extensionPaths: extensionPaths
164               });
165             });
166           });
167         };
168         var getExtensionFromRole = function (role, callback) {
169           var id = role.userAccountRole,
170             roleModel = new SYS.UserAccountRole();
171
172           roleModel.fetch({id: id,
173             username: X.options.databaseServer.user,
174             database: req.session.passport.user.organization,
175             success: function (model, result) {
176               var extensions = _.map(model.get("grantedExtensions"), function (ext) {
177                 return ext.extension;
178               });
179               callback(null, extensions);
180             },
181             error: function (err) {
182               callback(err);
183             }
184           });
185         };
186         var extensions = _.map(user.get("grantedExtensions"), function (ext) {
187           return ext.extension;
188         });
189         // Add the extensions that the user gets from his roles
190         var userAccountRoles = user.get("grantedUserAccountRoles");
191         if (userAccountRoles.length > 0) {
192           // we're going to have to do more async calls to get all the
193           // extensions related to these roles.
194           async.map(userAccountRoles, getExtensionFromRole, function (err, results) {
195             if (err) {
196               res.send({isError: true, message: "error in extension route"});
197               return;
198             }
199             // add all role-granted extensions that are not already there
200             _.each(results, function (result) {
201               _.each(result, function (newExt) {
202                 var preExistingExt = _.find(extensions, function (currentExt) {
203                   return currentExt.id === newExt.id;
204                 });
205                 if (!preExistingExt) {
206                   extensions.push(newExt);
207                 }
208               });
209             });
210             sendExtensions(res, extensions);
211           });
212
213         } else {
214           // no second async call necessary
215           sendExtensions(res, extensions);
216         }
217       };
218
219     user.fetch({
220       id: req.session.passport.user.username,
221       success: fetchSuccess,
222       error: fetchError,
223       username: X.options.databaseServer.user,
224       database: req.session.passport.user.organization
225     });
226   };
227
228   exports.serveDebug = function (req, res) {
229     req.viewName = "debug";
230     serveApp(req, res);
231   };
232 }());