Merge pull request #1576 from shackbarth/publish
[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           extensions.sort(function (ext1, ext2) {
107             if (ext1.loadOrder !== ext2.loadOrder) {
108               return ext1.loadOrder - ext2.loadOrder;
109             } else {
110               return ext1.name > ext2.name ? 1 : -1;
111             }
112           });
113           var uuids = _.map(extensions, function (ext) {
114             var sortedModels = _.sortBy(ext.codeInfo, function (codeInfo) {
115               return -1 * getVersionSize(codeInfo.version);
116             });
117             if (sortedModels[0]) {
118               return sortedModels[0].uuid;
119             } else {
120               X.log("Could not find uuid for extension " + ext.description);
121               return null;
122             }
123           });
124           uuids = _.compact(uuids); // eliminate any null values
125           var extensionPaths = _.compact(_.map(extensions, function (ext) {
126             var locationName = ext.location.indexOf("/") === 0 ?
127               path.join(ext.location, "source") :
128               "/" + ext.location;
129             return path.join(locationName, ext.name);
130           }));
131           getCoreUuid('js', req.session.passport.user.organization, function (err, jsUuid) {
132             if (err) {
133               res.send({isError: true, error: err});
134               return;
135             }
136             getCoreUuid('css', req.session.passport.user.organization, function (err, cssUuid) {
137               if (err) {
138                 res.send({isError: true, error: err});
139                 return;
140               }
141               res.render(req.viewName || 'app', {
142                 org: req.session.passport.user.organization,
143                 coreJs: jsUuid,
144                 coreCss: cssUuid,
145                 extensions: uuids,
146                 extensionPaths: extensionPaths
147               });
148             });
149           });
150         };
151         var getExtensionFromRole = function (role, callback) {
152           var id = role.userAccountRole,
153             roleModel = new SYS.UserAccountRole();
154
155           roleModel.fetch({id: id,
156             username: X.options.databaseServer.user,
157             database: req.session.passport.user.organization,
158             success: function (model, result) {
159               var extensions = _.map(model.get("grantedExtensions"), function (ext) {
160                 return ext.extension;
161               });
162               callback(null, extensions);
163             },
164             error: function (err) {
165               callback(err);
166             }
167           });
168         };
169         var extensions = _.map(user.get("grantedExtensions"), function (ext) {
170           return ext.extension;
171         });
172         // Add the extensions that the user gets from his roles
173         var userAccountRoles = user.get("grantedUserAccountRoles");
174         if (userAccountRoles.length > 0) {
175           // we're going to have to do more async calls to get all the
176           // extensions related to these roles.
177           async.map(userAccountRoles, getExtensionFromRole, function (err, results) {
178             if (err) {
179               res.send({isError: true, message: "error in extension route"});
180               return;
181             }
182             // add all role-granted extensions that are not already there
183             _.each(results, function (result) {
184               _.each(result, function (newExt) {
185                 var preExistingExt = _.find(extensions, function (currentExt) {
186                   return currentExt.id === newExt.id;
187                 });
188                 if (!preExistingExt) {
189                   extensions.push(newExt);
190                 }
191               });
192             });
193             sendExtensions(res, extensions);
194           });
195
196         } else {
197           // no second async call necessary
198           sendExtensions(res, extensions);
199         }
200       };
201
202     user.fetch({
203       id: req.session.passport.user.username,
204       success: fetchSuccess,
205       error: fetchError,
206       username: X.options.databaseServer.user,
207       database: req.session.passport.user.organization
208     });
209   };
210
211   exports.serveDebug = function (req, res) {
212     req.viewName = "debug";
213     serveApp(req, res);
214   };
215 }());