server/marketplace.js   C
last analyzed

Complexity

Total Complexity 54
Complexity/F 2.35

Size

Lines of Code 265
Function Count 23

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 2
dl 0
loc 265
rs 6.8539
c 0
b 0
f 0
wmc 54
mnd 2
bc 47
fnc 23
bpm 2.0434
cpm 2.3478
noi 24

14 Functions

Rating   Name   Duplication   Size   Complexity  
A marketplace.js ➔ gitClone 0 4 1
A marketplace.js ➔ getCommits 0 9 1
B marketplace.js ➔ installURL 0 42 4
B marketplace.js ➔ filter 0 10 6
B Router.post(ꞌ/portal/storeꞌ) 0 21 5
A marketplace.js ➔ getCommitMessage 0 3 1
B marketplace.js ➔ rename 0 33 5
A marketplace.js ➔ getMostRecentCommit 0 3 1
A marketplace.js ➔ create 0 15 3
A marketplace.js ➔ init 0 12 2
A marketplace.js ➔ clone 0 10 4
A marketplace.js ➔ refresh 0 13 1
A marketplace.js ➔ install 0 5 3
A Router.get(ꞌ/portal/storeꞌ) 0 4 1

How to fix   Complexity   

Complexity

Complex classes like server/marketplace.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
2
var fs      = require('fs-extra');
3
var request = require('request');
4
var express = require('express');
5
var extend  = require('extend');
6
var AdmZip  = require('adm-zip'); 
7
var path    = require('path');
8
9
// ------------------------------------------
10
//  CONSTRUCTOR
11
// ------------------------------------------
12
13
var TMP  = '';
14
var init = function(){
15
  info('Starting Marketplace ...');
16
  
17
  // Create temp plugin directory
18
  TMP = SARAH.ConfigManager.PLUGIN+'/tmp'; 
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
19
  if (!fs.existsSync(TMP)){ fs.mkdirSync(TMP); }
20
  
21
  // Refresh remote plugins
22
  refresh();
23
  
24
  return Marketplace;
25
}
26
27
// ------------------------------------------
28
//  MARKETPLACE
29
// ------------------------------------------
30
31
var MARKETPLACE = 'http://plugins.sarah.encausse.net';
32
33
var cache = {};
34
var refresh = function(){
35
  request({ 
36
    'uri' : MARKETPLACE, 
37
    'json' : true,
38
    'headers': {'user-agent': SARAH.USERAGENT} 
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
39
  }, 
40
  function (err, response, json){
41
    if (err || response.statusCode != 200) {
42
      return warn("Can't retrieve remote plugins");
43
    }
44
    cache = json;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
45
  });
46
}
47
48
49
// ------------------------------------------
50
//  SEARCH
51
// ------------------------------------------
52
53
var filter = function(name, plugin, search){
54
  if (!search) return true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
55
  search = search.toLowerCase();
56
  
57
  for (var key in plugin){
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
58
    if (name.toLowerCase().indexOf(search) >= 0) return true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
59
    if(plugin[key] && plugin[key].toLowerCase().indexOf(search) >= 0) return true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
60
  }
61
  return false;
62
}
63
64
// ------------------------------------------
65
//  INSTALL
66
// ------------------------------------------
67
68
69
var install = function(name, callback){
70
  var market = cache[name]; 
71
  if (!market || !market.dl){ return callback(); }
72
  installURL(market.dl, SARAH.ConfigManager.PLUGIN+'/'+name, callback);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
73
}
74
75
var installURL = function(url, path, callback){
76
  
77
  // Delete previous zip if exists
78
  var archive = TMP + '/' + 'tmp.zip';
79
  var drop    = TMP + '/' + 'archive';
80
  if ( fs.existsSync(archive)){ fs.unlinkSync(archive); }
81
  if ( fs.existsSync(drop))   { fs.removeSync(drop);    }
82
  if (!fs.existsSync(drop))   { fs.mkdirSync(drop);     }
83
  info('Downloading %s to store...', url, drop, path);
84
  
85
  // Download file
86
  request({ 
87
    'uri' : url, 
88
    'headers': {'user-agent': SARAH.USERAGENT} 
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
89
  }, 
90
  function (err, response, json){
0 ignored issues
show
Unused Code introduced by
The parameter json is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
91
    if (err || response.statusCode != 200) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if err || response.statusCode != 200 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
92
      warn("Can't download remote plugin", url, err);
93
      return callback();
94
    }
95
  }).pipe(fs.createWriteStream(archive)).on('close', function(){
96
    
97
    var zip = new AdmZip(archive);
98
    zip.extractAllTo(drop, true);
99
    
100
    // Recursive function 
101
    var copy = function(root){ 
102
      var files = fs.readdirSync(root);
103
      if (files.length != 1){ 
104
        fs.rename(root, path, function(){}); 
105
      } else {
106
        root += '/' + files[0];
107
        if (fs.statSync(root).isDirectory()){ copy(root); }
108
      }
109
    }
110
    
111
    copy(drop); // Perform copy
112
    fs.removeSync(drop); // Clean remaining stuff
113
    callback();
114
    
115
  });
116
}
117
118
// ------------------------------------------
119
//  GITHUB
120
// ------------------------------------------
121
122
var TEMPLATE = 'http://template.sarah.encausse.net';
123
var clone = function(body, callback){
124
  if (!body.name) { return callback('store.github.msg.name'); }
125
  if (!body.url)  { return callback('store.github.msg.url');  }
126
  
127
  var name = body.name.toLowerCase().replace(' ', '_');
128
  var path = SARAH.ConfigManager.PLUGIN+'/'+name;
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
129
  if (fs.existsSync(path)){ return callback('store.msg.exists'); }
130
  
131
  gitClone(body.url, path, callback);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
132
}
133
134
var create = function(body, callback){
135
  if (!body.name) { return callback('store.new.msg.name'); }
136
  
137
  var name = body.name.toLowerCase().replace(' ', '_');
138
  var path = SARAH.ConfigManager.PLUGIN+'/'+body.name;
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
139
  if (fs.existsSync(path)){ return callback('store.msg.exists'); }
140
  
141
  gitClone(TEMPLATE, path, function(){
142
    rename(path, { 
143
      "template": name, 
144
      "template_description": body.description 
145
    });
146
    callback('store.msg.ok');
147
  });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
148
}
149
150
/*
151
var Git = require("nodegit");
152
var gitClone = function(giturl, path, callback){
153
  info('Cloning repository %s at %s', giturl, path);
154
  Git.Clone(giturl, path).then(function(repository) {
155
    callback('store.msg.ok');
156
  });
157
}*/
158
159
var gitClone = function(giturl, path, callback){
160
  var url = giturl.replace('.git', '/archive/master.zip');
161
  installURL(url, path, callback);
162
}
163
164
var rename = function(file, matches){
165
  
166
  // Directory
167
  if (fs.statSync(file).isDirectory()){
168
    var files = fs.readdirSync(file);
169
    for(var i in files){
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
170
      rename(file+'/'+files[i], matches);
171
    }
172
    return;
173
  }
174
  
175
  // Replace filename
176
  var name = path.basename(file);
177
  if (name.indexOf('template') >= 0){
178
    var dest = path.dirname(file) + '/' + name.replace(/template/g, matches.template);
179
    fs.renameSync(file, dest);
180
    file = dest;
181
  }
182
  
183
  // Clean path
184
  file = path.normalize(file);
185
  
186
  var ext = path.extname(file);
187
  if (!(ext == '.html' || ext == '.ejs' || ext == '.js' || ext == '.xml' || ext == '.prop' || ext == '.less' || ext == '.md')){ return; }
188
  
189
  // Search and replace
190
  var data = fs.readFileSync(file, 'utf8');
191
      data = data.replace(/template_description/g, matches.template_description || '');
192
      data = data.replace(/template_version/g,     matches.template_version     || '');
193
      data = data.replace(/template/g,             matches.template);
194
      data = data.replace(/Template/g,             matches.template.capitalize());
195
  fs.writeFile(file, data, 'utf8');
196
}
197
198
199
// ------------------------------------------
200
//  TEST
201
// ------------------------------------------
202
203
var getMostRecentCommit = function(repository) {
204
  return repository.getBranchCommit("master");
205
};
206
207
var getCommitMessage = function(commit) {
208
  return commit.message();
209
};
210
211
var getCommits = function(name, callback){
212
  var path = SARAH.ConfigManager.PLUGIN+'/'+name;
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
213
  var err  = function(){ callback(); }
214
  
215
  Git.Repository.open(path)
0 ignored issues
show
Bug introduced by
The variable Git seems to be never declared. If this is a global, consider adding a /** global: Git */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
216
    .then(getMostRecentCommit, err)
217
    .then(getCommitMessage,    err)
218
    .then(function(message) { callback(message); }, err);
219
}
220
221
// ------------------------------------------
222
//  ROUTER
223
// ------------------------------------------
224
225
var Router = express.Router();
226
227
Router.get('/portal/store', function(req, res, next) {
0 ignored issues
show
Unused Code introduced by
The parameter next is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
228
  res.locals.sidebar.nav[0].active = true;
229
  res.render('store/store.ejs', { marketplace : cache });
230
});
231
232
Router.post('/portal/store', function(req, res, next) {
0 ignored issues
show
Unused Code introduced by
The parameter next is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
233
  
234
  var callback = function(msg){
235
    var url = '/portal/store';
236
        url += msg ? '?msg='+msg : '';
237
    res.redirect(url);
238
  }
239
  
240
  var name = req.body.opInstall;
241
  if (name){ install(name, callback); }
242
  
243
  var name = req.body.opDelete;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable name already seems to be declared on line 240. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
244
  if (name){ SARAH.PluginManager.remove(name, callback); }
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
245
  
246
  var op = req.body.opCreate;
247
  if (op){ create(req.body, callback); }
248
  
249
  var op = req.body.opGitClone;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable op already seems to be declared on line 246. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
250
  if (op){ clone(req.body, callback); }
251
  
252
});
253
254
// ------------------------------------------
255
//  PUBLIC
256
// ------------------------------------------
257
258
var Marketplace = {
259
  'init'       : init,
260
  'filter'     : filter,
261
  'getCommits' : getCommits,
262
  'Router'     : Router
263
}
264
265
// Exports Manager
266
exports.init = Marketplace.init;
267
268