<commit_before>var networking = require("./networking");
var logging = require("./logging");
var config = require("./config");
var cache = require("./cache");
var skins = require("./skins");
var renders = require("./renders");
var fs = require("fs");

// 0098cb60-fa8e-427c-b299-793cbd302c9a
var valid_user_id = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
var hash_pattern = /[0-9a-f]+$/;

// gets the hash from the textures.minecraft.net +url+
function get_hash(url) {
  return hash_pattern.exec(url)[0].toLowerCase();
}

function store_skin(rid, userId, profile, details, callback) {
  networking.get_skin_url(rid, userId, profile, function(url) {
    if (url) {
      var skin_hash = get_hash(url);
      if (details && details.skin === skin_hash) {
        cache.update_timestamp(rid, userId, skin_hash, function(err) {
          callback(err, skin_hash);
        });
      } else {
        logging.log(rid + "new skin hash: " + skin_hash);
        var facepath = __dirname + "/../" + config.faces_dir + skin_hash + ".png";
        var helmpath = __dirname + "/../" + config.helms_dir + skin_hash + ".png";
        fs.exists(facepath, function(exists) {
          if (exists) {
            logging.log(rid + "skin already exists, not downloading");
            callback(null, skin_hash);
          } else {
            networking.get_from(rid, url, function(img, response, err) {
              if (err || !img) {
                callback(err, null);
              } else {
                skins.extract_face(img, facepath, function(err) {
                  if (err) {
                    logging.error(rid + err.stack);
                    callback(err, null);
                  } else {
                    logging.log(rid + "face extracted");
                    skins.extract_helm(rid, facepath, img, helmpath, function(err) {
                      logging.log(rid + "helm extracted");
                      logging.debug(rid + helmpath);
                      callback(err, skin_hash);
                    });
                  }
                });
              }
            });
          }
        });
      }
    } else {
      callback(null, null);
    }
  });
}

function store_cape(rid, userId, profile, details, callback) {
  networking.get_cape_url(rid, userId, profile, function(url) {
    if (url) {
      var cape_hash = get_hash(url);
      if (details && details.cape === cape_hash) {
        cache.update_timestamp(rid, userId, cape_hash, function(err) {
          callback(err, cape_hash);
        });
      } else {
        logging.log(rid + "new cape hash: " + cape_hash);
        var capepath = __dirname + "/../" + config.capes_dir + cape_hash + ".png";
        fs.exists(capepath, function(exists) {
          if (exists) {
            logging.log(rid + "cape already exists, not downloading");
            callback(null, cape_hash);
          } else {
            networking.get_from(rid, url, function(img, response, err) {
              if (err || !img) {
                logging.error(rid + err.stack);
                callback(err, null);
              } else {
                skins.save_image(img, capepath, function(err) {
                  logging.log(rid + "cape saved");
                  callback(err, cape_hash);
                });
              }
            });
          }
        });
      }
    } else {
      callback(null, null);
    }
  });
}

// used by store_images to queue simultaneous requests for identical userId
// the first request has to be completed until all others are continued
var currently_running = [];
// calls back all queued requests that match userId and type
function callback_for(userId, type, err, hash) {
  var req_count = 0;
  for (var i = 0; i < currently_running.length; i++) {
    var current = currently_running[i];
    if (current.userid === userId && current.type === type) {
      req_count++;
      if (req_count !== 1) {
        // otherwise this would show up on single/first requests, too
        logging.debug(current.rid + "queued " + type + " request continued");
      }
      currently_running.splice(i, 1); // remove from array
      current.callback(err, hash);
      i--;
    }
  }
  if (req_count > 1) {
    logging.debug(req_count + " simultaneous requests for " + userId);
  }
}

// returns true if any object in +arr+ has +value+ as +property+
function deep_property_check(arr, property, value) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i][property] === value) {
      return true;
    }
  }
  return false;
}

// downloads the images for +userId+ while checking the cache
// status based on +details+. +type+ specifies which
// image type should be called back on
// +callback+ contains error, image hash
function store_images(rid, userId, details, type, callback) {
  var is_uuid = userId.length > 16;
  var new_hash = {
    rid: rid,
    userid: userId,
    type: type,
    callback: callback
  };
  if (!deep_property_check(currently_running, "userid", userId)) {
    currently_running.push(new_hash);
    networking.get_profile(rid, (is_uuid ? userId : null), function(err, profile) {
      if (err || (is_uuid && !profile)) {
        callback_for(userId, type, err, null);
      } else {
        store_skin(rid, userId, profile, details, function(err, skin_hash) {
          cache.save_hash(rid, userId, skin_hash, null, function(cache_err) {
            callback_for(userId, "skin", (err || cache_err), skin_hash);
            store_cape(rid, userId, profile, details, function(err, cape_hash) {
              cache.save_hash(rid, userId, skin_hash, cape_hash, function(cache_err) {
                callback_for(userId, "cape", (err || cache_err), cape_hash);
              });
            });
          });
        });
      }
    });
  } else {
    logging.log(rid + "ID already being processed, adding to queue");
    currently_running.push(new_hash);
  }
}

var exp = {};

// returns true if the +userId+ is a valid userId or username
// the userId may be not exist, however
exp.id_valid = function(userId) {
  return valid_user_id.test(userId);
};

// decides whether to get an image from disk or to download it
// callback contains error, status, hash
// the status gives information about how the image was received
//  -1: "error"
//   0: "none" - cached as null
//   1: "cached" - found on disk
//   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
//   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
exp.get_image_hash = function(rid, userId, type, callback) {
  cache.get_details(userId, function(err, details) {
    var hash = details !== null ? (type === "skin" ? details.skin : details.cape) : null;
    if (err) {
      callback(err, -1, null);
    } else {
      if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
        // use cached image
        logging.log(rid + "userId cached & recently updated");
        callback(null, (hash ? 1 : 0), hash);
      } else {
        // download image
        if (details) {
          logging.log(rid + "userId cached, but too old");
        } else {
          logging.log(rid + "userId not cached");
        }
        store_images(rid, userId, details, type, function(err, new_hash) {
          if (err) {
            callback(err, -1, details && hash);
          } else {
            var status = details && (hash === new_hash) ? 3 : 2;
            logging.debug(rid + "old hash: " + (details && hash));
            logging.log(rid + "new hash: " + new_hash);
            callback(null, status, new_hash);
          }
        });
      }
    }
  });
};

// handles requests for +userId+ avatars with +size+
// callback contains error, status, image buffer, skin hash
// image is the user's face+helm when helm is true, or the face otherwise
// for status, see get_image_hash
exp.get_avatar = function(rid, userId, helm, size, callback) {
  exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
    if (skin_hash) {
      var facepath = __dirname + "/../" + config.faces_dir + skin_hash + ".png";
      var helmpath = __dirname + "/../" + config.helms_dir + skin_hash + ".png";
      var filepath = facepath;
      fs.exists(helmpath, function(exists) {
        if (helm && exists) {
          filepath = helmpath;
        }
        skins.resize_img(filepath, size, function(img_err, result) {
          if (img_err) {
            callback(img_err, -1, null, skin_hash);
          } else {
            // we might have a hash although an error occured
            // (e.g. Mojang servers not reachable, using outdated hash)
            callback(err, (err ? -1 : status), result, skin_hash);
          }
        });
      });
    } else {
      // hash is null when userId has no skin
      callback(err, status, null, null);
    }
  });
};

// handles requests for +userId+ skins
// callback contains error, skin hash, image buffer
exp.get_skin = function(rid, userId, callback) {
  logging.log(rid + "skin request");
  exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
    var skinpath = __dirname + "/../" + config.skins_dir + skin_hash + ".png";
    fs.exists(skinpath, function(exists) {
      if (exists) {
        logging.log(rid + "skin already exists, not downloading");
        skins.open_skin(rid, skinpath, function(err, img) {
          callback(err, skin_hash, img);
        });
      } else {
        networking.save_texture(rid, skin_hash, skinpath, function(err, response, img) {
          callback(err, skin_hash, img);
        });
      }
    });
  });
};

function get_type(helm, body) {
  var text = body ? "body" : "head";
  return helm ? text + "helm" : text;
}

// handles creations of 3D renders
// callback contains error, skin hash, image buffer
exp.get_render = function(rid, userId, scale, helm, body, callback) {
  exp.get_skin(rid, userId, function(err, skin_hash, img) {
    if (!skin_hash) {
      callback(err, -1, skin_hash, null);
      return;
    }
    var renderpath = __dirname + "/../" + config.renders_dir + skin_hash + "-" + scale + "-" + get_type(helm, body) + ".png";
    fs.exists(renderpath, function(exists) {
      if (exists) {
        renders.open_render(rid, renderpath, function(err, img) {
          callback(err, 1, skin_hash, img);
        });
        return;
      } else {
        if (!img) {
          callback(err, 0, skin_hash, null);
          return;
        }
        renders.draw_model(rid, img, scale, helm, body, function(err, img) {
          if (err) {
            callback(err, -1, skin_hash, null);
          } else if (!img) {
            callback(null, 0, skin_hash, null);
          } else {
            fs.writeFile(renderpath, img, "binary", function(err) {
              if (err) {
                logging.log(rid + err.stack);
              }
              callback(null, 2, skin_hash, img);
            });
          }
        });
      }
    });
  });
};

// handles requests for +userId+ capes
// callback contains error, cape hash, image buffer
exp.get_cape = function(rid, userId, callback) {
  logging.log(rid + "cape request");
  exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash) {
    if (!cape_hash) {
      callback(err, null, null);
      return;
    }
    var capepath = __dirname + "/../" + config.capes_dir + cape_hash + ".png";
    fs.exists(capepath, function(exists) {
      if (exists) {
        logging.log(rid + "cape already exists, not downloading");
        skins.open_skin(rid, capepath, function(err, img) {
          callback(err, cape_hash, img);
        });
      } else {
        networking.save_texture(rid, cape_hash, capepath, function(err, response, img) {
          if (response && response.statusCode === 404) {
            callback(err, cape_hash, null);
          } else {
            callback(err, cape_hash, img);
          }
        });
      }
    });
  });
};

module.exports = exp;
<commit_msg>cache non-existing UUIDs, fixes #93<commit_after>var networking = require("./networking");
var logging = require("./logging");
var config = require("./config");
var cache = require("./cache");
var skins = require("./skins");
var renders = require("./renders");
var fs = require("fs");

// 0098cb60-fa8e-427c-b299-793cbd302c9a
var valid_user_id = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
var hash_pattern = /[0-9a-f]+$/;

// gets the hash from the textures.minecraft.net +url+
function get_hash(url) {
  return hash_pattern.exec(url)[0].toLowerCase();
}

function store_skin(rid, userId, profile, details, callback) {
  networking.get_skin_url(rid, userId, profile, function(url) {
    if (url) {
      var skin_hash = get_hash(url);
      if (details && details.skin === skin_hash) {
        cache.update_timestamp(rid, userId, skin_hash, function(err) {
          callback(err, skin_hash);
        });
      } else {
        logging.log(rid + "new skin hash: " + skin_hash);
        var facepath = __dirname + "/../" + config.faces_dir + skin_hash + ".png";
        var helmpath = __dirname + "/../" + config.helms_dir + skin_hash + ".png";
        fs.exists(facepath, function(exists) {
          if (exists) {
            logging.log(rid + "skin already exists, not downloading");
            callback(null, skin_hash);
          } else {
            networking.get_from(rid, url, function(img, response, err) {
              if (err || !img) {
                callback(err, null);
              } else {
                skins.extract_face(img, facepath, function(err) {
                  if (err) {
                    logging.error(rid + err.stack);
                    callback(err, null);
                  } else {
                    logging.log(rid + "face extracted");
                    skins.extract_helm(rid, facepath, img, helmpath, function(err) {
                      logging.log(rid + "helm extracted");
                      logging.debug(rid + helmpath);
                      callback(err, skin_hash);
                    });
                  }
                });
              }
            });
          }
        });
      }
    } else {
      callback(null, null);
    }
  });
}

function store_cape(rid, userId, profile, details, callback) {
  networking.get_cape_url(rid, userId, profile, function(url) {
    if (url) {
      var cape_hash = get_hash(url);
      if (details && details.cape === cape_hash) {
        cache.update_timestamp(rid, userId, cape_hash, function(err) {
          callback(err, cape_hash);
        });
      } else {
        logging.log(rid + "new cape hash: " + cape_hash);
        var capepath = __dirname + "/../" + config.capes_dir + cape_hash + ".png";
        fs.exists(capepath, function(exists) {
          if (exists) {
            logging.log(rid + "cape already exists, not downloading");
            callback(null, cape_hash);
          } else {
            networking.get_from(rid, url, function(img, response, err) {
              if (err || !img) {
                logging.error(rid + err.stack);
                callback(err, null);
              } else {
                skins.save_image(img, capepath, function(err) {
                  logging.log(rid + "cape saved");
                  callback(err, cape_hash);
                });
              }
            });
          }
        });
      }
    } else {
      callback(null, null);
    }
  });
}

// used by store_images to queue simultaneous requests for identical userId
// the first request has to be completed until all others are continued
var currently_running = [];
// calls back all queued requests that match userId and type
function callback_for(userId, type, err, hash) {
  var req_count = 0;
  for (var i = 0; i < currently_running.length; i++) {
    var current = currently_running[i];
    if (current.userid === userId && current.type === type) {
      req_count++;
      if (req_count !== 1) {
        // otherwise this would show up on single/first requests, too
        logging.debug(current.rid + "queued " + type + " request continued");
      }
      currently_running.splice(i, 1); // remove from array
      current.callback(err, hash);
      i--;
    }
  }
  if (req_count > 1) {
    logging.debug(req_count + " simultaneous requests for " + userId);
  }
}

// returns true if any object in +arr+ has +value+ as +property+
function deep_property_check(arr, property, value) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i][property] === value) {
      return true;
    }
  }
  return false;
}

// downloads the images for +userId+ while checking the cache
// status based on +details+. +type+ specifies which
// image type should be called back on
// +callback+ contains error, image hash
function store_images(rid, userId, details, type, callback) {
  var is_uuid = userId.length > 16;
  var new_hash = {
    rid: rid,
    userid: userId,
    type: type,
    callback: callback
  };
  if (!deep_property_check(currently_running, "userid", userId)) {
    currently_running.push(new_hash);
    networking.get_profile(rid, (is_uuid ? userId : null), function(err, profile) {
      if (err || (is_uuid && !profile)) {
        if (!err && !profile) {
          cache.save_hash(rid, userId, null, null, function(cache_err) {
            // we have no profile, so we have neither skin nor cape
            callback_for(userId, "skin", cache_err, null);
            callback_for(userId, "cape", cache_err, null);
          });
        } else {
          callback_for(userId, type, err, null);
        }
      } else {
        store_skin(rid, userId, profile, details, function(err, skin_hash) {
          cache.save_hash(rid, userId, skin_hash, null, function(cache_err) {
            callback_for(userId, "skin", (err || cache_err), skin_hash);
            store_cape(rid, userId, profile, details, function(err, cape_hash) {
              cache.save_hash(rid, userId, skin_hash, cape_hash, function(cache_err) {
                callback_for(userId, "cape", (err || cache_err), cape_hash);
              });
            });
          });
        });
      }
    });
  } else {
    logging.log(rid + "ID already being processed, adding to queue");
    currently_running.push(new_hash);
  }
}

var exp = {};

// returns true if the +userId+ is a valid userId or username
// the userId may be not exist, however
exp.id_valid = function(userId) {
  return valid_user_id.test(userId);
};

// decides whether to get an image from disk or to download it
// callback contains error, status, hash
// the status gives information about how the image was received
//  -1: "error"
//   0: "none" - cached as null
//   1: "cached" - found on disk
//   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
//   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
exp.get_image_hash = function(rid, userId, type, callback) {
  cache.get_details(userId, function(err, details) {
    var hash = details !== null ? (type === "skin" ? details.skin : details.cape) : null;
    if (err) {
      callback(err, -1, null);
    } else {
      if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
        // use cached image
        logging.log(rid + "userId cached & recently updated");
        callback(null, (hash ? 1 : 0), hash);
      } else {
        // download image
        if (details) {
          logging.log(rid + "userId cached, but too old");
        } else {
          logging.log(rid + "userId not cached");
        }
        store_images(rid, userId, details, type, function(err, new_hash) {
          if (err) {
            callback(err, -1, details && hash);
          } else {
            var status = details && (hash === new_hash) ? 3 : 2;
            logging.debug(rid + "old hash: " + (details && hash));
            logging.log(rid + "new hash: " + new_hash);
            callback(null, status, new_hash);
          }
        });
      }
    }
  });
};

// handles requests for +userId+ avatars with +size+
// callback contains error, status, image buffer, skin hash
// image is the user's face+helm when helm is true, or the face otherwise
// for status, see get_image_hash
exp.get_avatar = function(rid, userId, helm, size, callback) {
  exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
    if (skin_hash) {
      var facepath = __dirname + "/../" + config.faces_dir + skin_hash + ".png";
      var helmpath = __dirname + "/../" + config.helms_dir + skin_hash + ".png";
      var filepath = facepath;
      fs.exists(helmpath, function(exists) {
        if (helm && exists) {
          filepath = helmpath;
        }
        skins.resize_img(filepath, size, function(img_err, result) {
          if (img_err) {
            callback(img_err, -1, null, skin_hash);
          } else {
            // we might have a hash although an error occured
            // (e.g. Mojang servers not reachable, using outdated hash)
            callback(err, (err ? -1 : status), result, skin_hash);
          }
        });
      });
    } else {
      // hash is null when userId has no skin
      callback(err, status, null, null);
    }
  });
};

// handles requests for +userId+ skins
// callback contains error, skin hash, image buffer
exp.get_skin = function(rid, userId, callback) {
  logging.log(rid + "skin request");
  exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
    var skinpath = __dirname + "/../" + config.skins_dir + skin_hash + ".png";
    fs.exists(skinpath, function(exists) {
      if (exists) {
        logging.log(rid + "skin already exists, not downloading");
        skins.open_skin(rid, skinpath, function(err, img) {
          callback(err, skin_hash, img);
        });
      } else {
        networking.save_texture(rid, skin_hash, skinpath, function(err, response, img) {
          callback(err, skin_hash, img);
        });
      }
    });
  });
};

function get_type(helm, body) {
  var text = body ? "body" : "head";
  return helm ? text + "helm" : text;
}

// handles creations of 3D renders
// callback contains error, skin hash, image buffer
exp.get_render = function(rid, userId, scale, helm, body, callback) {
  exp.get_skin(rid, userId, function(err, skin_hash, img) {
    if (!skin_hash) {
      callback(err, -1, skin_hash, null);
      return;
    }
    var renderpath = __dirname + "/../" + config.renders_dir + skin_hash + "-" + scale + "-" + get_type(helm, body) + ".png";
    fs.exists(renderpath, function(exists) {
      if (exists) {
        renders.open_render(rid, renderpath, function(err, img) {
          callback(err, 1, skin_hash, img);
        });
        return;
      } else {
        if (!img) {
          callback(err, 0, skin_hash, null);
          return;
        }
        renders.draw_model(rid, img, scale, helm, body, function(err, img) {
          if (err) {
            callback(err, -1, skin_hash, null);
          } else if (!img) {
            callback(null, 0, skin_hash, null);
          } else {
            fs.writeFile(renderpath, img, "binary", function(err) {
              if (err) {
                logging.log(rid + err.stack);
              }
              callback(null, 2, skin_hash, img);
            });
          }
        });
      }
    });
  });
};

// handles requests for +userId+ capes
// callback contains error, cape hash, image buffer
exp.get_cape = function(rid, userId, callback) {
  logging.log(rid + "cape request");
  exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash) {
    if (!cape_hash) {
      callback(err, null, null);
      return;
    }
    var capepath = __dirname + "/../" + config.capes_dir + cape_hash + ".png";
    fs.exists(capepath, function(exists) {
      if (exists) {
        logging.log(rid + "cape already exists, not downloading");
        skins.open_skin(rid, capepath, function(err, img) {
          callback(err, cape_hash, img);
        });
      } else {
        networking.save_texture(rid, cape_hash, capepath, function(err, response, img) {
          if (response && response.statusCode === 404) {
            callback(err, cape_hash, null);
          } else {
            callback(err, cape_hash, img);
          }
        });
      }
    });
  });
};

module.exports = exp;
<|endoftext|>


<commit_before>var net = require('net');
var EventEmitter = require('events').EventEmitter;
var sys = require('sys');
var xml = require('./xml');
var StreamParser = require('./stream_parser');

var NS_XMPP_TLS = exports.NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls';
var NS_STREAM = exports.NS_STREAM = 'http://etherx.jabber.org/streams';
var NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams';

/** A note on events: this base class will emit 'rawStanza' and leaves
    'stanza' to Client & Component. Therefore we won't confuse the
    user with stanzas before authentication has finished.
*/
function Connection(socket) {
    var self = this;
    EventEmitter.call(this);

    this.charset = 'UTF-8';
    this.xmlns = { stream: NS_STREAM };

    this.socket = socket || new net.Stream();
    this.socket.addListener('data', function(data) {
        self.onData(data);
    });
    this.socket.addListener('end', function() {
        self.onEnd();
    });
    var proxyEvent = function(event) {
        self.socket.addListener(event, function() {
            self.emit.apply(self, Array.prototype.splice.call(arguments, 0, 0, event));
        });
    };
    proxyEvent('data');  // let them sniff
    proxyEvent('drain');
    proxyEvent('close');
}

sys.inherits(Connection, EventEmitter);
exports.Connection = Connection;

// Defaults
Connection.prototype.charset = 'UTF-8';
Connection.prototype.allowTLS = true;

/** Climbs the stanza up if a child was passed,
    but you can send strings and buffers too.
*/
Connection.prototype.send = function(stanza) {
    if (!this.socket.writable) {
        this.socket.end();
        return;
    }

    if (stanza.root) {
        var el = this.rmStreamNs(stanza.root());
        var socket = this.socket;
        el.write(function(s) { socket.write(s); });
        return el;
    } else {
        this.socket.write(stanza);
        return stanza;
    }
};

Connection.prototype.startParser = function() {
    var self = this;
    this.parser = new StreamParser.StreamParser(this.charset, this.maxStanzaSize);

    this.parser.addListener('start', function(attrs) {
        self.streamAttrs = attrs;
        /* We need those xmlns often, store them extra */
        self.streamNsAttrs = {};
        for(var k in attrs) {
        if (k == 'xmlns' ||
            k.substr(0, 6) == 'xmlns:')
                self.streamNsAttrs[k] = attrs[k];
        }

        /* Notify in case we don't wait for <stream:features/>
           (Component or non-1.0 streams)
         */
        self.emit('streamStart', attrs);
    });
    this.parser.addListener('stanza', function(stanza) {
        self.onStanza(self.addStreamNs(stanza));
    });
    this.parser.addListener('error', function(e) {
        self.error(e.condition || 'internal-server-error', e.message);
    });
    this.parser.addListener('end', function() {
        self.end();
    });
};

Connection.prototype.stopParser = function() {
    delete this.parser;
};

Connection.prototype.startStream = function() {
    var attrs = {};
    for(var k in this.xmlns) {
        if (this.xmlns.hasOwnProperty(k)) {
            if (!k)
                attrs.xmlns = this.xmlns[k];
            else
                attrs['xmlns:' + k] = this.xmlns[k];
        }
    }
    if (this.xmppVersion)
        attrs.version = this.xmppVersion;
    if (this.streamTo)
        attrs.to = this.streamTo;
    if (this.streamId)
        attrs.id = this.streamId;

    var el = new xml.Element('stream:stream', attrs);
    // make it non-empty to cut the closing tag
    el.t(' ');
    var s = el.toString();
    this.send(s.substr(0, s.indexOf(' </stream:stream>')));
};

Connection.prototype.onData = function(data) {
    if (this.parser)
        this.parser.write(data);
};

Connection.prototype.setSecure = function(credentials) {
    var self = this;
    this.stopParser();
    this.socket.setSecure(credentials || this.credentials);
    this.socket.addListener('secure', function() {
        self.startParser();
    });
};

/**
 * This is not an event listener, but takes care of the TLS handshake
 * before 'rawStanza' events are emitted to the derived classes.
 */
Connection.prototype.onStanza = function(stanza) {
    if (stanza.is('error', NS_STREAM)) {
        /* TODO: extract error text */
        this.emit('error', stanza);
    } else if (stanza.is('features', NS_STREAM) &&
               this.allowTLS &&
               stanza.getChild('starttls', NS_XMPP_TLS)) {
        /* Signal willingness to perform TLS handshake */
        this.send(new xml.Element('starttls', { xmlns: NS_XMPP_TLS }));
    } else if (this.allowTLS &&
               stanza.is('proceed', NS_XMPP_TLS)) {
        /* Server is waiting for TLS handshake */
        this.setSecure();
    } else {
        this.emit('rawStanza', stanza);
    }
};

/**
 *  Add stream xmlns to a stanza, so the user can check for
 * 'jabber:client' etc.
 */
Connection.prototype.addStreamNs = function(stanza) {
    for(var k in this.streamNsAttrs) {
        if (!stanza.attrs[k])
            stanza.attrs[k] = this.streamNsAttrs[k];
    }
    return stanza;
};

/**
 * Remove superfluous xmlns that were aleady declared in
 * <stream:stream>
 */
Connection.prototype.rmStreamNs = function(stanza) {
    for(var k in this.streamNsAttrs) {
        if (stanza.attrs[k] == this.streamNsAttrs[k])
            delete stanza.attrs[k];
    }
    return stanza;
};

/**
 * Connection has been ended by remote, we will not get any incoming
 * 'data' events. Alternatively, used for 'error' event.
 *
 * We don't deal with half-closed connections and end our half too.
 */
Connection.prototype.onEnd = function() {
    this.stopParser();
    this.end();
};

/**
 * XMPP-style end connection for user
 */
Connection.prototype.end = function() {
    if (this.socket.writable)
        this.socket.write('</stream:stream>');
    this.socket.end();
    // stopParser will called on 'end'/'error' event
};

/**
 * End connection with stream error.
 * Emits 'error' event too.
 *
 * @param {String} condition XMPP error condition, see RFC3920 4.7.3. Defined Conditions
 * @param {String} text Optional error message
 */
Connection.prototype.error = function(condition, message) {
    this.emit('error', new Error(message));

    if (!this.socket.writable)
        return;

    var e = new xml.Element('stream:error');
    e.c(condition, { xmlns: NS_XMPP_STREAMS });
    if (message)
        e.c('text', { xmlns: NS_XMPP_STREAMS,
                      'xml:lang': 'en' }).
        t(message);

    this.send(e);
    this.end();
};
<commit_msg>connection rmStreamNs(): remove our xmlns for outgoing stanzas<commit_after>var net = require('net');
var EventEmitter = require('events').EventEmitter;
var sys = require('sys');
var xml = require('./xml');
var StreamParser = require('./stream_parser');

var NS_XMPP_TLS = exports.NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls';
var NS_STREAM = exports.NS_STREAM = 'http://etherx.jabber.org/streams';
var NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams';

/** A note on events: this base class will emit 'rawStanza' and leaves
    'stanza' to Client & Component. Therefore we won't confuse the
    user with stanzas before authentication has finished.
*/
function Connection(socket) {
    var self = this;
    EventEmitter.call(this);

    this.charset = 'UTF-8';
    this.xmlns = { stream: NS_STREAM };

    this.socket = socket || new net.Stream();
    this.socket.addListener('data', function(data) {
        self.onData(data);
    });
    this.socket.addListener('end', function() {
        self.onEnd();
    });
    var proxyEvent = function(event) {
        self.socket.addListener(event, function() {
            self.emit.apply(self, Array.prototype.splice.call(arguments, 0, 0, event));
        });
    };
    proxyEvent('data');  // let them sniff
    proxyEvent('drain');
    proxyEvent('close');
}

sys.inherits(Connection, EventEmitter);
exports.Connection = Connection;

// Defaults
Connection.prototype.charset = 'UTF-8';
Connection.prototype.allowTLS = true;

/** Climbs the stanza up if a child was passed,
    but you can send strings and buffers too.
*/
Connection.prototype.send = function(stanza) {
    if (!this.socket.writable) {
        this.socket.end();
        return;
    }

    if (stanza.root) {
        var el = this.rmStreamNs(stanza.root());
        var socket = this.socket;
        el.write(function(s) { socket.write(s); });
        return el;
    } else {
        this.socket.write(stanza);
        return stanza;
    }
};

Connection.prototype.startParser = function() {
    var self = this;
    this.parser = new StreamParser.StreamParser(this.charset, this.maxStanzaSize);

    this.parser.addListener('start', function(attrs) {
        self.streamAttrs = attrs;
        /* We need those xmlns often, store them extra */
        self.streamNsAttrs = {};
        for(var k in attrs) {
        if (k == 'xmlns' ||
            k.substr(0, 6) == 'xmlns:')
                self.streamNsAttrs[k] = attrs[k];
        }

        /* Notify in case we don't wait for <stream:features/>
           (Component or non-1.0 streams)
         */
        self.emit('streamStart', attrs);
    });
    this.parser.addListener('stanza', function(stanza) {
        self.onStanza(self.addStreamNs(stanza));
    });
    this.parser.addListener('error', function(e) {
        self.error(e.condition || 'internal-server-error', e.message);
    });
    this.parser.addListener('end', function() {
        self.end();
    });
};

Connection.prototype.stopParser = function() {
    delete this.parser;
};

Connection.prototype.startStream = function() {
    var attrs = {};
    for(var k in this.xmlns) {
        if (this.xmlns.hasOwnProperty(k)) {
            if (!k)
                attrs.xmlns = this.xmlns[k];
            else
                attrs['xmlns:' + k] = this.xmlns[k];
        }
    }
    if (this.xmppVersion)
        attrs.version = this.xmppVersion;
    if (this.streamTo)
        attrs.to = this.streamTo;
    if (this.streamId)
        attrs.id = this.streamId;

    var el = new xml.Element('stream:stream', attrs);
    // make it non-empty to cut the closing tag
    el.t(' ');
    var s = el.toString();
    this.send(s.substr(0, s.indexOf(' </stream:stream>')));
};

Connection.prototype.onData = function(data) {
    if (this.parser)
        this.parser.write(data);
};

Connection.prototype.setSecure = function(credentials) {
    var self = this;
    this.stopParser();
    this.socket.setSecure(credentials || this.credentials);
    this.socket.addListener('secure', function() {
        self.startParser();
    });
};

/**
 * This is not an event listener, but takes care of the TLS handshake
 * before 'rawStanza' events are emitted to the derived classes.
 */
Connection.prototype.onStanza = function(stanza) {
    if (stanza.is('error', NS_STREAM)) {
        /* TODO: extract error text */
        this.emit('error', stanza);
    } else if (stanza.is('features', NS_STREAM) &&
               this.allowTLS &&
               stanza.getChild('starttls', NS_XMPP_TLS)) {
        /* Signal willingness to perform TLS handshake */
        this.send(new xml.Element('starttls', { xmlns: NS_XMPP_TLS }));
    } else if (this.allowTLS &&
               stanza.is('proceed', NS_XMPP_TLS)) {
        /* Server is waiting for TLS handshake */
        this.setSecure();
    } else {
        this.emit('rawStanza', stanza);
    }
};

/**
 *  Add stream xmlns to a stanza, so the user can check for
 * 'jabber:client' etc.
 */
Connection.prototype.addStreamNs = function(stanza) {
    for(var k in this.streamNsAttrs) {
        if (!stanza.attrs[k])
            stanza.attrs[k] = this.streamNsAttrs[k];
    }
    return stanza;
};

/**
 * Remove superfluous xmlns that were aleady declared in
 * our <stream:stream>
 */
Connection.prototype.rmStreamNs = function(stanza) {
    console.log({preRm:stanza.toString()});
    for(var k in this.xmlns) {
	var attr = k ? 'xmlns:' + k : 'xmlns';
        if (stanza.attrs[attr] == this.xmlns[k])
            delete stanza.attrs[attr];
    }
    console.log({postRm:stanza.toString()});
    return stanza;
};

/**
 * Connection has been ended by remote, we will not get any incoming
 * 'data' events. Alternatively, used for 'error' event.
 *
 * We don't deal with half-closed connections and end our half too.
 */
Connection.prototype.onEnd = function() {
    this.stopParser();
    this.end();
};

/**
 * XMPP-style end connection for user
 */
Connection.prototype.end = function() {
    if (this.socket.writable)
        this.socket.write('</stream:stream>');
    this.socket.end();
    // stopParser will called on 'end'/'error' event
};

/**
 * End connection with stream error.
 * Emits 'error' event too.
 *
 * @param {String} condition XMPP error condition, see RFC3920 4.7.3. Defined Conditions
 * @param {String} text Optional error message
 */
Connection.prototype.error = function(condition, message) {
    this.emit('error', new Error(message));

    if (!this.socket.writable)
        return;

    var e = new xml.Element('stream:error');
    e.c(condition, { xmlns: NS_XMPP_STREAMS });
    if (message)
        e.c('text', { xmlns: NS_XMPP_STREAMS,
                      'xml:lang': 'en' }).
        t(message);

    this.send(e);
    this.end();
};
<|endoftext|>


<commit_before>const fs = require('fs-extra');
const prettyBytes = require('pretty-bytes');
const moment = require('moment');

const sourceDir = process.argv[2];
const indexFile = 'index.html';
const indexTpl = indexFile + '.tpl';
const tableTpl = 'table.html.tpl';
const styleFile = 'style.css';
const listFile = 'list.txt';

const clockSince = moment('2017-04');

function numberFormat(n) {
  return new Intl.NumberFormat().format(n);
}

function fileInfo(gameCounts, variant, n) {
  const path = sourceDir + '/' + variant + '/' + n;
  return fs.stat(path).then(s => {
    const dateStr = n.replace(/.+(\d{4}-\d{2})\.pgn\.bz2/, '$1');
    const m = moment(dateStr);
    const shortName = n.replace(/.+(\d{4}-\d{2}.+)$/, '$1');
    const hasClock = m.unix() >= clockSince.unix();
    return {
      name: n,
      shortName: shortName,
      path: path,
      size: s.size,
      date: m,
      clock: hasClock,
      games: parseInt(gameCounts[n]) || 0
    };
  });
}

function getGameCounts(variant) {
  return fs.readFile(sourceDir + '/' + variant + '/counts.txt', { encoding: 'utf8' }).then(c => {
    var gameCounts = {};
    c.split('\n').map(l => l.trim()).forEach(line => {
      if (line !== '') gameCounts[line.split(' ')[0]] = line.split(' ')[1];
    });
    return gameCounts;
  });
}

function getFiles(variant) {
  return function(gameCounts) {
    return fs.readdir(sourceDir + '/' + variant).then(items => {
      return Promise.all(
        items.filter(n => n.includes('.pgn.bz2')).map(n => fileInfo(gameCounts, variant, n))
      );
    }).then(items => items.sort((a, b) => b.date.unix() - a.date.unix()));
  }
}

function renderTable(files, variant) {
  return files.map(f => {
    return `

    ${f.date.format('MMMM YYYY')}
    ${prettyBytes(f.size)}
    ${f.games ? numberFormat(f.games) : '?'}
    ${f.clock ? '✔' : ''}
    ${f.shortName}

`;
  }).join('\n');
}

function renderTotal(files) {
  return `

  Total: ${files.length} files
  ${prettyBytes(files.map(f => f.size).reduce((a, b) => a + b))}
  ${numberFormat(files.map(f => f.games).reduce((a, b) => a + b))}

`;
}

function renderList(files, variant) {
  return files.map(f => {
    return `https://database.lichess.org/${variant}/${f.name}`;
  }).join('\n');
}

function processVariantAndReturnTable(variant, template) {
  return getGameCounts(variant).then(getFiles(variant)).then(files => {
    return fs.writeFile(sourceDir + '/' + variant + '/' + listFile, renderList(files, variant)).then(_ => {
      return template
        .replace(//, numberFormat(files.map(f => f.games).reduce((a, b) => a + b)))
        .replace(//, renderTable(files, variant))
        .replace(//, renderTotal(files))
        .replace(//g, variant);
    });
  });
}

function replaceVariant(variant, tableTemplate) {
  return function(fullTemplate) {
    return processVariantAndReturnTable(variant, tableTemplate).then(tbl => {
      return fullTemplate.replace('', tbl);
    });
  };
}

process.on('unhandledRejection', r => console.log(r));

Promise.all([
  fs.readFile(indexTpl, { encoding: 'utf8' }),
  fs.readFile(tableTpl, { encoding: 'utf8' }),
  fs.readFile(styleFile, { encoding: 'utf8' })
]).then(arr => {
  const rv = function(variant) { return replaceVariant(variant, arr[1]); };
  return rv('standard')(arr[0])
    .then(rv('antichess'))
    .then(rv('atomic'))
    .then(rv('chess960'))
    .then(rv('crazyhouse'))
    .then(rv('horde'))
    .then(rv('kingOfTheHill'))
    .then(rv('racingKings'))
    .then(rv('threeCheck'))
    .then(rendered => {
      return fs.writeFile(sourceDir + '/' + indexFile, rendered.replace(//, arr[2]));
    });
});
<commit_msg>fix behaviour on empty files<commit_after>const fs = require('fs-extra');
const prettyBytes = require('pretty-bytes');
const moment = require('moment');

const sourceDir = process.argv[2];
const indexFile = 'index.html';
const indexTpl = indexFile + '.tpl';
const tableTpl = 'table.html.tpl';
const styleFile = 'style.css';
const listFile = 'list.txt';

const clockSince = moment('2017-04');

function numberFormat(n) {
  return new Intl.NumberFormat().format(n);
}

function fileInfo(gameCounts, variant, n) {
  const path = sourceDir + '/' + variant + '/' + n;
  return fs.stat(path).then(s => {
    const dateStr = n.replace(/.+(\d{4}-\d{2})\.pgn\.bz2/, '$1');
    const m = moment(dateStr);
    const shortName = n.replace(/.+(\d{4}-\d{2}.+)$/, '$1');
    const hasClock = m.unix() >= clockSince.unix();
    return {
      name: n,
      shortName: shortName,
      path: path,
      size: s.size,
      date: m,
      clock: hasClock,
      games: parseInt(gameCounts[n]) || 0
    };
  });
}

function getGameCounts(variant) {
  return fs.readFile(sourceDir + '/' + variant + '/counts.txt', { encoding: 'utf8' }).then(c => {
    var gameCounts = {};
    c.split('\n').map(l => l.trim()).forEach(line => {
      if (line !== '') gameCounts[line.split(' ')[0]] = line.split(' ')[1];
    });
    return gameCounts;
  });
}

function getFiles(variant) {
  return function(gameCounts) {
    return fs.readdir(sourceDir + '/' + variant).then(items => {
      return Promise.all(
        items.filter(n => n.includes('.pgn.bz2')).map(n => fileInfo(gameCounts, variant, n))
      );
    }).then(items => items.sort((a, b) => b.date.unix() - a.date.unix()));
  }
}

function renderTable(files, variant) {
  return files.map(f => {
    return `

    ${f.date.format('MMMM YYYY')}
    ${prettyBytes(f.size)}
    ${f.games ? numberFormat(f.games) : '?'}
    ${f.clock ? '✔' : ''}
    ${f.shortName}

`;
  }).join('\n');
}

function renderTotal(files) {
  return `

  Total: ${files.length} files
  ${prettyBytes(files.map(f => f.size).reduce((a, b) => a + b, 0))}
  ${numberFormat(files.map(f => f.games).reduce((a, b) => a + b, 0))}

`;
}

function renderList(files, variant) {
  return files.map(f => {
    return `https://database.lichess.org/${variant}/${f.name}`;
  }).join('\n');
}

function processVariantAndReturnTable(variant, template) {
  return getGameCounts(variant).then(getFiles(variant)).then(files => {
    return fs.writeFile(sourceDir + '/' + variant + '/' + listFile, renderList(files, variant)).then(_ => {
      return template
        .replace(//, numberFormat(files.map(f => f.games).reduce((a, b) => a + b, 0)))
        .replace(//, renderTable(files, variant))
        .replace(//, renderTotal(files))
        .replace(//g, variant);
    });
  });
}

function replaceVariant(variant, tableTemplate) {
  return function(fullTemplate) {
    return processVariantAndReturnTable(variant, tableTemplate).then(tbl => {
      return fullTemplate.replace('', tbl);
    });
  };
}

process.on('unhandledRejection', r => console.log(r));

Promise.all([
  fs.readFile(indexTpl, { encoding: 'utf8' }),
  fs.readFile(tableTpl, { encoding: 'utf8' }),
  fs.readFile(styleFile, { encoding: 'utf8' })
]).then(arr => {
  const rv = function(variant) { return replaceVariant(variant, arr[1]); };
  return rv('standard')(arr[0])
    .then(rv('antichess'))
    .then(rv('atomic'))
    .then(rv('chess960'))
    .then(rv('crazyhouse'))
    .then(rv('horde'))
    .then(rv('kingOfTheHill'))
    .then(rv('racingKings'))
    .then(rv('threeCheck'))
    .then(rendered => {
      return fs.writeFile(sourceDir + '/' + indexFile, rendered.replace(//, arr[2]));
    });
});
<|endoftext|>


<commit_before>'use strict';

exports.start = function () {

if (process.env.NEW_RELIC_ENABLED === "true") require('newrelic');

var env = process.env.NODE_ENV || 'development';

var _ = require('lodash');
var Hapi = require('hapi');
var Blankie = require('blankie');
var Scooter = require('scooter');
var Chairo = require('chairo');
var path = require('path');
var options = require('./options.' + env + '.js');
var locale = require('locale');
var languages = require('./config/languages.js');
var cacheTimes = require('./config/cache-times');

require('./lib/dust-i18n.js');

var availableLocales = new locale.Locales(_.pluck(languages, 'code'));
var server = new Hapi.Server(options.hapi)
var port = process.env.PORT || 8000
var host = process.env.HOSTNAME || 'localhost';
var protocol = process.env.PROTOCOL || 'http';
var hostWithPort = protocol + '://' + host + ':' + port;

function checkHapiPluginError (name) {
  return function (error) {
    if (error) {
      console.error('Failed loading a Hapi plugin: "' + name + '".');
      throw error;
    }
  };
}

// Set up HAPI

server.connection({
  port: port,
  // According to the HTTP spec and Chrome audit tool, Cache-Control headers should match what
  // would be sent for 200 when a 304 (Not Modified) is sent.
  routes: {
    cache: { statuses: [200,304] },
    cors: { origin: [ hostWithPort, 'https://changex.org' ] }
  }
});

server.views({
  engines: { dust: require('hapi-dust') },
  path: path.join(__dirname, './public/templates'),
  partialsPath: path.join(__dirname, './public/templates')
});

server.ext('onPreAuth', function (request, reply) {
  var localesFormReq = (request.state && request.state.NG_TRANSLATE_LANG_KEY && request.state.NG_TRANSLATE_LANG_KEY.replace(/\"/g, ''))
    || request.headers['accept-language'];

  var requestLocales = new locale.Locales(localesFormReq);

  request.locals = {
    context: {
      locality: requestLocales.best(availableLocales).code
    }
  };

  return reply.continue();
});

// Handler for 404/401
server.ext('onPreResponse', function (request, reply) {
  var status = request.response.statusCode;

  if (status !== 404 && status !== 401) {
    return reply.continue();
  }

  return reply.view('errors/404', request.locals);
});

// Handler for 500
server.ext('onPreResponse', function (request, reply) {
  var headerStatus = _.get(request, 'response.statusCode', 500);
  var bodyStatus = _.get(request, 'response.output.payload.statusCode', undefined);

  if (headerStatus !== 500 && bodyStatus !== 500) {
    return reply.continue();
  }

  // Display full error message if not in production environment.
  if (env !== 'production') {
    return reply.continue();
  }

  // Otherwise, give a generic error reply to hide errors in production.
  return reply.view('errors/500', request.locals);
});

// TODO Using stream here causes responses from seneca-web to be buffered, which may impact performance.
//      However, most of them aren't large sized responses, so the benefit of Etag outweighs that penalty.
//      Implementing better streaming support in hapi-etags may be fairly straightforward using Etag in the
//      Trailer rather than Header... - wprl
server.register({ register: require('hapi-etags'), options: { varieties: ['plain', 'buffer', 'stream'] } }, checkHapiPluginError('hapi-etags'));

server.register(Scooter, function (err) {
  checkHapiPluginError('scooter')(err);

  server.register({ register: Blankie, options: {
    childSrc: "'none'",
    connectSrc: "'self'",
    defaultSrc: "'none'",
    fontSrc: "'self' http://fonts.gstatic.com https://fonts.gstatic.com",
    frameSrc: "https://www.google.com",
    frameAncestors: "'none'",
    imgSrc: "'self' data: http://52.17.20.218 http://*.coderdojo.com https://*.coderdojo.com https://www.google-analytics.com https://*.gstatic.com https://*.googleapis.com https://chart.apis.google.com https://*.googlecode.com http://*.googlecode.com",
    manifestSrc: "'none'",
    mediaSrc: "'none'",
    objectSrc: "'none'",
    reflectedXss: 'block',
    scriptSrc: "'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com http://www.google-analytics.com https://maps.gstatic.com https://www.gstatic.com https://widget.intercom.io https://js.intercomcdn.com https://www.google.com https://apis.google.com",
    styleSrc: "'self' 'unsafe-inline' http://fonts.googleapis.com https://fonts.googleapis.com"
  }}, checkHapiPluginError('blankie'));
});

server.register({ register: require('./controllers') }, checkHapiPluginError('CoderDojo controllers'));

// Serve CSS files.
server.register({
  register: require('hapi-less'),
  options: {
    home: path.join(__dirname, './public/css'),
    route: '/css/{filename*}',
    config: { cache: { privacy: 'public', expiresIn: cacheTimes.short } },
    less: { compress: true }
  }
}, checkHapiPluginError('hapi-less'));

// Set up Chairo and seneca, then start the server.
server.register({ register: Chairo, options: options }, function (err) {
  checkHapiPluginError('Chairo')(err);

  server.register({ register: require('chairo-cache'), options: { cacheName: 'cd-cache' } }, function (err) {
    checkHapiPluginError('chairo-cache')(err);

    var seneca = server.seneca;

    seneca
      .use('ng-web')
      .use(require('../lib/users/user.js'))
      .use('auth')
      .use('user-roles')
      .use('web-access')
      .use(require('../lib/charter/cd-charter.js'))
      .use(require('../lib/dojos/cd-dojos.js'))
      .use(require('../lib/countries/cd-countries.js'))
      .use(require('../lib/users/cd-users.js'))
      .use(require('../lib/agreements/cd-agreements.js'))
      .use(require('../lib/badges/cd-badges.js'))
      .use(require('../lib/profiles/cd-profiles.js'))
      .use(require('../lib/events/cd-events.js'))
      .use(require('../lib/oauth2/cd-oauth2.js'))
      .use(require('../lib/config/cd-config.js'), options.webclient)
      .use(require('../lib/sys/cd-sys.js'));

    _.each(options.client, function(opts) {
      seneca.client(opts);
    });

    // // Potentially useful extra logging.

    // seneca.logroute( {level:'all' });

    // // capture seneca messages - leaving this here as we *may* do something with it
    // // if the debug level json is not good enough logging.
    // seneca.sub({}, captureAllMessages);
    // function captureAllMessages(args) {
    //   console.log('*** captured = ', JSON.stringify(args));
    // }

    // Use seneca-web middleware with Hapi.
    server.register({
      register: require('hapi-seneca'),
      options: {
        cors: true,
        session: {
          /*store: sessionStore, */ // TODO remote session store
          secret: options.session.secret,
          name: 'CD.ZENPLATFORM',
          saveUninitialized: true,
          resave: true
        }
      }
    }, function (err) {
      checkHapiPluginError('hapi-seneca')(err);
      server.start(function() {
        console.log('[%s] Listening on http://localhost:%d', env, port);
      });
    });
  });
});

};
<commit_msg>Allow apis.google.com to be http or https<commit_after>'use strict';

exports.start = function () {

if (process.env.NEW_RELIC_ENABLED === "true") require('newrelic');

var env = process.env.NODE_ENV || 'development';

var _ = require('lodash');
var Hapi = require('hapi');
var Blankie = require('blankie');
var Scooter = require('scooter');
var Chairo = require('chairo');
var path = require('path');
var options = require('./options.' + env + '.js');
var locale = require('locale');
var languages = require('./config/languages.js');
var cacheTimes = require('./config/cache-times');

require('./lib/dust-i18n.js');

var availableLocales = new locale.Locales(_.pluck(languages, 'code'));
var server = new Hapi.Server(options.hapi)
var port = process.env.PORT || 8000
var host = process.env.HOSTNAME || 'localhost';
var protocol = process.env.PROTOCOL || 'http';
var hostWithPort = protocol + '://' + host + ':' + port;

function checkHapiPluginError (name) {
  return function (error) {
    if (error) {
      console.error('Failed loading a Hapi plugin: "' + name + '".');
      throw error;
    }
  };
}

// Set up HAPI

server.connection({
  port: port,
  // According to the HTTP spec and Chrome audit tool, Cache-Control headers should match what
  // would be sent for 200 when a 304 (Not Modified) is sent.
  routes: {
    cache: { statuses: [200,304] },
    cors: { origin: [ hostWithPort, 'https://changex.org' ] }
  }
});

server.views({
  engines: { dust: require('hapi-dust') },
  path: path.join(__dirname, './public/templates'),
  partialsPath: path.join(__dirname, './public/templates')
});

server.ext('onPreAuth', function (request, reply) {
  var localesFormReq = (request.state && request.state.NG_TRANSLATE_LANG_KEY && request.state.NG_TRANSLATE_LANG_KEY.replace(/\"/g, ''))
    || request.headers['accept-language'];

  var requestLocales = new locale.Locales(localesFormReq);

  request.locals = {
    context: {
      locality: requestLocales.best(availableLocales).code
    }
  };

  return reply.continue();
});

// Handler for 404/401
server.ext('onPreResponse', function (request, reply) {
  var status = request.response.statusCode;

  if (status !== 404 && status !== 401) {
    return reply.continue();
  }

  return reply.view('errors/404', request.locals);
});

// Handler for 500
server.ext('onPreResponse', function (request, reply) {
  var headerStatus = _.get(request, 'response.statusCode', 500);
  var bodyStatus = _.get(request, 'response.output.payload.statusCode', undefined);

  if (headerStatus !== 500 && bodyStatus !== 500) {
    return reply.continue();
  }

  // Display full error message if not in production environment.
  if (env !== 'production') {
    return reply.continue();
  }

  // Otherwise, give a generic error reply to hide errors in production.
  return reply.view('errors/500', request.locals);
});

// TODO Using stream here causes responses from seneca-web to be buffered, which may impact performance.
//      However, most of them aren't large sized responses, so the benefit of Etag outweighs that penalty.
//      Implementing better streaming support in hapi-etags may be fairly straightforward using Etag in the
//      Trailer rather than Header... - wprl
server.register({ register: require('hapi-etags'), options: { varieties: ['plain', 'buffer', 'stream'] } }, checkHapiPluginError('hapi-etags'));

server.register(Scooter, function (err) {
  checkHapiPluginError('scooter')(err);

  server.register({ register: Blankie, options: {
    childSrc: "'none'",
    connectSrc: "'self'",
    defaultSrc: "'none'",
    fontSrc: "'self' http://fonts.gstatic.com https://fonts.gstatic.com",
    frameSrc: "https://www.google.com",
    frameAncestors: "'none'",
    imgSrc: "'self' data: http://52.17.20.218 http://*.coderdojo.com https://*.coderdojo.com https://www.google-analytics.com https://*.gstatic.com https://*.googleapis.com https://*.apis.google.com http://*.apis.google.com https://*.googlecode.com http://*.googlecode.com",
    manifestSrc: "'none'",
    mediaSrc: "'none'",
    objectSrc: "'none'",
    reflectedXss: 'block',
    scriptSrc: "'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com http://www.google-analytics.com https://maps.gstatic.com https://www.gstatic.com https://widget.intercom.io https://js.intercomcdn.com https://www.google.com https://apis.google.com",
    styleSrc: "'self' 'unsafe-inline' http://fonts.googleapis.com https://fonts.googleapis.com"
  }}, checkHapiPluginError('blankie'));
});

server.register({ register: require('./controllers') }, checkHapiPluginError('CoderDojo controllers'));

// Serve CSS files.
server.register({
  register: require('hapi-less'),
  options: {
    home: path.join(__dirname, './public/css'),
    route: '/css/{filename*}',
    config: { cache: { privacy: 'public', expiresIn: cacheTimes.short } },
    less: { compress: true }
  }
}, checkHapiPluginError('hapi-less'));

// Set up Chairo and seneca, then start the server.
server.register({ register: Chairo, options: options }, function (err) {
  checkHapiPluginError('Chairo')(err);

  server.register({ register: require('chairo-cache'), options: { cacheName: 'cd-cache' } }, function (err) {
    checkHapiPluginError('chairo-cache')(err);

    var seneca = server.seneca;

    seneca
      .use('ng-web')
      .use(require('../lib/users/user.js'))
      .use('auth')
      .use('user-roles')
      .use('web-access')
      .use(require('../lib/charter/cd-charter.js'))
      .use(require('../lib/dojos/cd-dojos.js'))
      .use(require('../lib/countries/cd-countries.js'))
      .use(require('../lib/users/cd-users.js'))
      .use(require('../lib/agreements/cd-agreements.js'))
      .use(require('../lib/badges/cd-badges.js'))
      .use(require('../lib/profiles/cd-profiles.js'))
      .use(require('../lib/events/cd-events.js'))
      .use(require('../lib/oauth2/cd-oauth2.js'))
      .use(require('../lib/config/cd-config.js'), options.webclient)
      .use(require('../lib/sys/cd-sys.js'));

    _.each(options.client, function(opts) {
      seneca.client(opts);
    });

    // // Potentially useful extra logging.

    // seneca.logroute( {level:'all' });

    // // capture seneca messages - leaving this here as we *may* do something with it
    // // if the debug level json is not good enough logging.
    // seneca.sub({}, captureAllMessages);
    // function captureAllMessages(args) {
    //   console.log('*** captured = ', JSON.stringify(args));
    // }

    // Use seneca-web middleware with Hapi.
    server.register({
      register: require('hapi-seneca'),
      options: {
        cors: true,
        session: {
          /*store: sessionStore, */ // TODO remote session store
          secret: options.session.secret,
          name: 'CD.ZENPLATFORM',
          saveUninitialized: true,
          resave: true
        }
      }
    }, function (err) {
      checkHapiPluginError('hapi-seneca')(err);
      server.start(function() {
        console.log('[%s] Listening on http://localhost:%d', env, port);
      });
    });
  });
});

};
<|endoftext|>


<commit_before>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// `$authService` SERVICE
// Defines methods for authenticating users by logging them in, de-authing by
// logging them out, and submitting registration applications.
//
// @pre     : networking libs must be initialized
// @pre     : sessionStorage must be available
//
// @attr    : apiUrl : String    : base API url of the resource
//
// @method  : login     : null      : Attempts to authenticate the user to the
// server.
// @method  : logout    : Promise   : Attempts to log user out from the server.
// @method  : register  : Promise   : Submits a user registration application to
// the server.
// @method  : loggedIn  : Promise   : Checks whether the user is logged in or
// not.
angular.module('wr.services')
.service('$authService', ['$http', '__env', function($http, __env) {
    var service = this;
    service.apiURL = __env.apiUrl + '/auth';

    // `login(email, pw)`
    // Attempts to authenticate the user to the server.
    //
    // @pre     : `email` must be a valid email of a user previously registered
    // with the server
    // @pre     : `pw` must be the valid password corresponding to the email
    //
    // @post    : [success] the user session object will be returned
    // @post    : [success] the user session object will be saved to
    // sessionStorage
    // @post    : [error] an error message will be returned
    //
    // @param   : email : String    : email of the user intending to log in
    // @param   : pw    : String    : password of the user intending to log in
    // @return  : Promise   : resolves to user object or rejects to error
    // message
    service.login = (email, pw) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/login`, {
                    email : email,
                    password : pw
                })
                .then((resp) => {
                    // save user session in sessionStorage
                    sessionStorage.setItem('wr-user', JSON.stringify(resp.data));
                    resolve(resp.data);
                })
                .catch(reject);
        });
    };

    // `logout()`
    // Attempts to log user out from the server.
    //
    // @pre     : user must be already logged-in
    //
    // @post    : the user session object will be deleted from sessionStorage
    //
    // @return  : Promise   : resolves to null
    service.logout = () => {
        return new Promise(function(resolve, reject) {
            // remove user from sessionStorage
            sessionStorage.removeItem('wr-user');

            // resolve no matter what
            $http
                .get(`${service.apiURL}/logout`)
                .then(resolve)
                .catch(resolve);

        });
    };

    // `register(email, pw, confirmPw)`
    // Submits a user registration application to the server.
    //
    // @pre     : `email` must be a valid unique email
    // @pre     : `pw` must be the intended password and non-empty
    // @pre     : `pw` must be a copy of the intended password
    //
    // @post    : [success] the `null` value will be returned
    // @post    : [error] an error message will be returned
    //
    // @param   : email     : String    : email of the user intending to
    // register
    // @param   : pw        : String    : password of the user intending to
    // register
    // @param   : confirmPw : String    : repeated password
    // @return  : Promise   : resolves to null or rejects to error message
    service.register = (email, pw, confirmPw) => {
        return new Promise(function(resolve, reject) {
            // check that pw === confirmPw
            if (pw !== confirmPw) {
                return reject({
                    message: "Passwords do not match."
                });
            }

            if (pw.trim().length === 0) {
                return reject({
                    message: "Password fields must not be empty."
                });
            }

            $http
                .post(`${service.apiURL}/register`, {
                    email : email,
                    password : pw,
                })
                .then(resolve)
                .catch(reject);
        });
    };

    // `isLoggedIn()`
    // Checks whether the user is logged in or not.
    //
    // @return  : Promise   : resolves to true if user is logged in, false
    // otherwise
    service.isLoggedIn = () => {
        return new Promise(function(resolve, reject) {
            resolve(!!sessionStorage.getItem('wr-user'));
        });
    };

    // `getUser()`
    // Retrieves the currently logged in user.
    //
    // @return  : Object    : returns the currently logged-in user's object, if
    // loggedin, otherwise null
    service.getUser = () => {
        return JSON.parse(sessionStorage.getItem('wr-user') || null);
    };

    // `escalateUser(id)`
    // Escalates a regular user to admin status.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be an admin
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to be escalated
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.escalateUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/escalate/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    // `deescalateUser(id)`
    // De-escalates an admin user to regular user status.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be a regular user
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to be deescalated
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.deescalateUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/deescalate/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    // `approveUser(id)`
    // Approves an applicant and makes them a regular user.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be a regular user
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to approve
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.approveUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/approve/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    return service;
}]);
}());
<commit_msg>Add empty object fallback for .getUser rather than null<commit_after>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// `$authService` SERVICE
// Defines methods for authenticating users by logging them in, de-authing by
// logging them out, and submitting registration applications.
//
// @pre     : networking libs must be initialized
// @pre     : sessionStorage must be available
//
// @attr    : apiUrl : String    : base API url of the resource
//
// @method  : login     : null      : Attempts to authenticate the user to the
// server.
// @method  : logout    : Promise   : Attempts to log user out from the server.
// @method  : register  : Promise   : Submits a user registration application to
// the server.
// @method  : loggedIn  : Promise   : Checks whether the user is logged in or
// not.
angular.module('wr.services')
.service('$authService', ['$http', '__env', function($http, __env) {
    var service = this;
    service.apiURL = __env.apiUrl + '/auth';

    // `login(email, pw)`
    // Attempts to authenticate the user to the server.
    //
    // @pre     : `email` must be a valid email of a user previously registered
    // with the server
    // @pre     : `pw` must be the valid password corresponding to the email
    //
    // @post    : [success] the user session object will be returned
    // @post    : [success] the user session object will be saved to
    // sessionStorage
    // @post    : [error] an error message will be returned
    //
    // @param   : email : String    : email of the user intending to log in
    // @param   : pw    : String    : password of the user intending to log in
    // @return  : Promise   : resolves to user object or rejects to error
    // message
    service.login = (email, pw) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/login`, {
                    email : email,
                    password : pw
                })
                .then((resp) => {
                    // save user session in sessionStorage
                    sessionStorage.setItem('wr-user', JSON.stringify(resp.data));
                    resolve(resp.data);
                })
                .catch(reject);
        });
    };

    // `logout()`
    // Attempts to log user out from the server.
    //
    // @pre     : user must be already logged-in
    //
    // @post    : the user session object will be deleted from sessionStorage
    //
    // @return  : Promise   : resolves to null
    service.logout = () => {
        return new Promise(function(resolve, reject) {
            // remove user from sessionStorage
            sessionStorage.removeItem('wr-user');

            // resolve no matter what
            $http
                .get(`${service.apiURL}/logout`)
                .then(resolve)
                .catch(resolve);

        });
    };

    // `register(email, pw, confirmPw)`
    // Submits a user registration application to the server.
    //
    // @pre     : `email` must be a valid unique email
    // @pre     : `pw` must be the intended password and non-empty
    // @pre     : `pw` must be a copy of the intended password
    //
    // @post    : [success] the `null` value will be returned
    // @post    : [error] an error message will be returned
    //
    // @param   : email     : String    : email of the user intending to
    // register
    // @param   : pw        : String    : password of the user intending to
    // register
    // @param   : confirmPw : String    : repeated password
    // @return  : Promise   : resolves to null or rejects to error message
    service.register = (email, pw, confirmPw) => {
        return new Promise(function(resolve, reject) {
            // check that pw === confirmPw
            if (pw !== confirmPw) {
                return reject({
                    message: "Passwords do not match."
                });
            }

            if (pw.trim().length === 0) {
                return reject({
                    message: "Password fields must not be empty."
                });
            }

            $http
                .post(`${service.apiURL}/register`, {
                    email : email,
                    password : pw,
                })
                .then(resolve)
                .catch(reject);
        });
    };

    // `isLoggedIn()`
    // Checks whether the user is logged in or not.
    //
    // @return  : Promise   : resolves to true if user is logged in, false
    // otherwise
    service.isLoggedIn = () => {
        return new Promise(function(resolve, reject) {
            resolve(!!sessionStorage.getItem('wr-user'));
        });
    };

    // `getUser()`
    // Retrieves the currently logged in user.
    //
    // @return  : Object    : returns the currently logged-in user's object, if
    // loggedin, otherwise {}
    service.getUser = () => {
        return JSON.parse(sessionStorage.getItem('wr-user')) || {};
    };

    // `escalateUser(id)`
    // Escalates a regular user to admin status.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be an admin
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to be escalated
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.escalateUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/escalate/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    // `deescalateUser(id)`
    // De-escalates an admin user to regular user status.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be a regular user
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to be deescalated
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.deescalateUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/deescalate/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    // `approveUser(id)`
    // Approves an applicant and makes them a regular user.
    //
    // @pre     : [success] the user submitting the request must be an admin
    // @pre     : [success] the user submitting the request must be an admin
    // @post    : [success] the user will be a regular user
    // @post    : [error] the user's status will not change
    // @post    : [error] the promise will reject to an error message
    //
    // @param   : id    : Number    : the id of the user to approve
    // @return  : Promise   : resolves to object with 'error' key false if
    // successful, true otherwise
    service.approveUser = (id) => {
        return new Promise(function(resolve, reject) {
            $http
                .post(`${service.apiURL}/approve/${id}`)
                .then(resolve)
                .catch(reject);
        });
    };

    return service;
}]);
}());
<|endoftext|>


<commit_before>import Ember from 'ember';
import Analytics from '../mixins/analytics';

/**
 * @module ember-preprints
 * @submodule components
 */

/**
 * Displays active preprint providers in a horizontal carousel with five providers per slide. Does not auto-advance.
 * Handles display on two pages: index (lightLogo=true) and discover (lightLogo=false).  If using elsewhere, need to add more customization
 * around how provider logos and links are built.
 *
 * Sample usage:
 * ```handlebars
 * {{provider-carousel
 *  providers=providers
}}
 * ```
 * @class provider-carousel
 */
export default Ember.Component.extend(Analytics, {
    _resizeListener: null,
    providers: Ember.A(), // Pass in preprint providers
    itemsPerSlide: 5, // Default
    lightLogo: true, // Light logos by default, for Index page.
    numProviders: Ember.computed('providers', function() {
        return this.get('providers').length;
    }),
    numSlides: Ember.computed('numProviders', 'itemsPerSlide', function() {
        return Math.ceil(this.get('numProviders')/this.get('itemsPerSlide'));
    }),
    slides: Ember.computed('numSlides', 'providers', 'itemsPerSlide', function() {
        const numSlides = this.get('numSlides');
        const itemsPerSlide = this.get('itemsPerSlide');
        return new Array(numSlides).fill().map((_, i) => {
            return this.get('providers').slice(i * itemsPerSlide, i * itemsPerSlide + itemsPerSlide);
        });
    }),
    setSlideItems: function() {
        // On xs screens, show one provider per slide. Otherwise, five.
        if (window.innerWidth < 768) {
            this.set('itemsPerSlide', 1);
        } else {
            this.set('itemsPerSlide', 5);
        }
    },
    didInsertElement: function () {
        // On xs screen, display one provider per slide
        Ember.$('.carousel').carousel();
    },
    init: function() {
        // Set resize listener so number of providers per slide can be changed
        this._super(...arguments);
        this.setSlideItems();
        this._resizeListener = Ember.run.bind(this, this.setSlideItems);
        Ember.$(window).on('resize', this._resizeListener);
    },
    willDestroy: function() {
        // Unbinds _resizeListener
        if (this._resizeListener) {
            Ember.$(window).off('resize', this._resizeListener);
        }
    }
});

<commit_msg>Styling.<commit_after>import Ember from 'ember';
import Analytics from '../mixins/analytics';

/**
 * @module ember-preprints
 * @submodule components
 */

/**
 * Displays active preprint providers in a horizontal carousel with five providers per slide. Does not auto-advance.
 * Handles display on two pages: index (lightLogo=true) and discover (lightLogo=false).  If using elsewhere, need to add more customization
 * around how provider logos and links are built.
 *
 * Sample usage:
 * ```handlebars
 * {{provider-carousel
 *  providers=providers
}}
 * ```
 * @class provider-carousel
 */
export default Ember.Component.extend(Analytics, {
    _resizeListener: null,
    providers: Ember.A(), // Pass in preprint providers
    itemsPerSlide: 5, // Default
    lightLogo: true, // Light logos by default, for Index page.
    numProviders: Ember.computed('providers', function() {
        return this.get('providers').length;
    }),
    numSlides: Ember.computed('numProviders', 'itemsPerSlide', function() {
        return Math.ceil(this.get('numProviders') / this.get('itemsPerSlide'));
    }),
    slides: Ember.computed('numSlides', 'providers', 'itemsPerSlide', function() {
        const numSlides = this.get('numSlides');
        const itemsPerSlide = this.get('itemsPerSlide');
        return new Array(numSlides).fill().map((_, i) => this.get('providers').slice(i * itemsPerSlide, i * itemsPerSlide + itemsPerSlide));
    }),
    setSlideItems: function() {
        // On xs screens, show one provider per slide. Otherwise, five.
        if (window.innerWidth < 768) {
            this.set('itemsPerSlide', 1);
        } else {
            this.set('itemsPerSlide', 5);
        }
    },
    didInsertElement: function () {
        // On xs screen, display one provider per slide
        Ember.$('.carousel').carousel();
    },
    init: function() {
        // Set resize listener so number of providers per slide can be changed
        this._super(...arguments);
        this.setSlideItems();
        this._resizeListener = Ember.run.bind(this, this.setSlideItems);
        Ember.$(window).on('resize', this._resizeListener);
    },
    willDestroy: function() {
        // Unbinds _resizeListener
        if (this._resizeListener) {
            Ember.$(window).off('resize', this._resizeListener);
        }
    }
});

<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/session-offerings';
import { translationMacro as t } from "ember-i18n";

const { Component, computed, inject, isPresent, RSVP } = Ember;
const { service } = inject;
const { alias, oneWay } = computed;
const { all } = RSVP;

export default Component.extend({
  saving: false,

  store: service(),

  i18n: service(),

  layout,

  classNames: ['session-offerings'],

  session: null,

  placeholderValue: t('sessions.titleFilterPlaceholder'),

  offerings: oneWay('session.offerings'),

  newButtonTitle: t('general.add'),

  offeringEditorOn: false,

  cohorts: alias('session.course.cohorts'),

  actions: {
    addSingleOffering({ startDate, endDate, room, learnerGroups, instructors, instructorGroups }) {
      const store = this.get('store');
      const session = this.get('session');
      let offering = store.createRecord('offering', { session, startDate, endDate, room, learnerGroups, instructors, instructorGroups });

      offering.save().then((offering) => {
        offering.get('learnerGroups').then((offeringlearnerGroups) => {
          learnerGroups.forEach((learnerGroup) => {
            offeringlearnerGroups.pushObject(learnerGroup);
          });
        });

        if (isPresent(instructors)) {
          offering.get('instructors').then((offeringInstructors) => {
            offeringInstructors.pushObjects(instructors);
          });
        }

        if (isPresent(instructorGroups)) {
          offering.get('instructorGroups').then((offeringInstructorGroups) => {
            offeringInstructorGroups.pushObjects(instructorGroups);
          });
        }
      });
    },

    addMultipleOfferings({ learnerGroups, startDate, endDate }) {
      this.set('saving', true);

      const store = this.get('store');
      const session = this.get('session');
      let offeringPromises = [];

      learnerGroups.forEach((learnerGroup) => {
        const room = learnerGroup.get('location') || 'TBD';
        let learnerGroups = [ learnerGroup ];
        let offering = store.createRecord('offering', { session, startDate, endDate, room, learnerGroups });

        offeringPromises.pushObject(offering.save());
      });

      all(offeringPromises).then((offerings) => {
        let promises = [];

        learnerGroups.forEach((learnerGroup, index) => {
          offerings[index].get('learnerGroups').then((offeringLearnerGroups) => {
            offeringLearnerGroups.pushObject(learnerGroup);
          });

          learnerGroup.get('instructors').then((defaultInstructors) => {
            if (isPresent(defaultInstructors)) {
              offerings[index].get('instructors').then((offeringInstructors) => {
                offeringInstructors.pushObjects(defaultInstructors);
              });
            }
          });

          learnerGroup.get('instructorGroups').then((defaultInstructorGroups) => {
            if (isPresent(defaultInstructorGroups)) {
              offerings[index].get('instructorGroups').then((offeringInstructorGroups) => {
                offeringInstructorGroups.pushObjects(defaultInstructorGroups);
              });
            }
          });
        });

        all(promises).finally(() => {
          this.set('saving', false);
        });
      });

      // MORE ELEGANT SOLUTION (FIX POLYMORPHIC RELATIONSHIP ERROR):
      //
      // const promises = learnerGroups.map((learnerGroup) => {
      //   const room = learnerGroup.get('location');
      //   const offering = store.createRecord('offering', { session, startDate, endDate, room });
      //   const offeringPromise = offering.save();
      //
      //   const learnerGroupsPromise = offeringPromise.then(() => {
      //       return offering.get('learnerGroups');
      //     })
      //     .then((groups) => {
      //       groups.pushObject('learnerGroup');
      //     });
      //
      //   const instructorsPromise = offeringPromise.then(() => {
      //       const instructors = offering.get('instructors');
      //       const defaultInstructors = learnerGroup.get('instructorUsers');
      //
      //       return hash({ instructors, defaultInstructors });
      //     })
      //     .then(({ instructors, defaultInstructors }) => {
      //       if (isPresent(defaultInstructors)) {
      //         instructors.pushObjects(defaultInstructors);
      //       }
      //     });
      //
      //   return all([ learnerGroupsPromise, instructorsPromise ]).then(() => {
      //     return offering.save();
      //   })
      // });
      //
      // all(promises).finally(() => {
      //   this.set('saving', false);
      // });
    },

    toggleEditor() {
      this.set('offeringEditorOn', !this.get('offeringEditorOn'));
    },

    closeEditor() {
      this.set('offeringEditorOn', false);
    }
  }
});
<commit_msg>Copy the start and end date objects so they are not shared<commit_after>import Ember from 'ember';
import layout from '../templates/components/session-offerings';
import { translationMacro as t } from "ember-i18n";

const { Component, computed, inject, isPresent, RSVP, copy } = Ember;
const { service } = inject;
const { alias, oneWay } = computed;
const { all } = RSVP;

export default Component.extend({
  saving: false,

  store: service(),

  i18n: service(),

  layout,

  classNames: ['session-offerings'],

  session: null,

  placeholderValue: t('sessions.titleFilterPlaceholder'),

  offerings: oneWay('session.offerings'),

  newButtonTitle: t('general.add'),

  offeringEditorOn: false,

  cohorts: alias('session.course.cohorts'),

  actions: {
    addSingleOffering({ startDate, endDate, room, learnerGroups, instructors, instructorGroups }) {
      const store = this.get('store');
      const session = this.get('session');
      let offering = store.createRecord('offering', { session, startDate, endDate, room, learnerGroups, instructors, instructorGroups });

      offering.save().then((offering) => {
        offering.get('learnerGroups').then((offeringlearnerGroups) => {
          learnerGroups.forEach((learnerGroup) => {
            offeringlearnerGroups.pushObject(learnerGroup);
          });
        });

        if (isPresent(instructors)) {
          offering.get('instructors').then((offeringInstructors) => {
            offeringInstructors.pushObjects(instructors);
          });
        }

        if (isPresent(instructorGroups)) {
          offering.get('instructorGroups').then((offeringInstructorGroups) => {
            offeringInstructorGroups.pushObjects(instructorGroups);
          });
        }
      });
    },

    addMultipleOfferings({ learnerGroups, startDate: sharedStartDateObj, endDate: sharedEndDateObj }) {
      this.set('saving', true);

      const store = this.get('store');
      const session = this.get('session');
      let offeringPromises = [];

      learnerGroups.forEach((learnerGroup) => {
        const room = learnerGroup.get('location') || 'TBD';
        const startDate = copy(sharedStartDateObj);
        const endDate = copy(sharedEndDateObj);
        let learnerGroups = [ learnerGroup ];
        let offering = store.createRecord('offering', { session, startDate, endDate, room, learnerGroups });

        offeringPromises.pushObject(offering.save());
      });

      all(offeringPromises).then((offerings) => {
        let promises = [];

        learnerGroups.forEach((learnerGroup, index) => {
          offerings[index].get('learnerGroups').then((offeringLearnerGroups) => {
            offeringLearnerGroups.pushObject(learnerGroup);
          });

          learnerGroup.get('instructors').then((defaultInstructors) => {
            if (isPresent(defaultInstructors)) {
              offerings[index].get('instructors').then((offeringInstructors) => {
                offeringInstructors.pushObjects(defaultInstructors);
              });
            }
          });

          learnerGroup.get('instructorGroups').then((defaultInstructorGroups) => {
            if (isPresent(defaultInstructorGroups)) {
              offerings[index].get('instructorGroups').then((offeringInstructorGroups) => {
                offeringInstructorGroups.pushObjects(defaultInstructorGroups);
              });
            }
          });
        });

        all(promises).finally(() => {
          this.set('saving', false);
        });
      });

      // MORE ELEGANT SOLUTION (FIX POLYMORPHIC RELATIONSHIP ERROR):
      //
      // const promises = learnerGroups.map((learnerGroup) => {
      //   const room = learnerGroup.get('location');
      //   const offering = store.createRecord('offering', { session, startDate, endDate, room });
      //   const offeringPromise = offering.save();
      //
      //   const learnerGroupsPromise = offeringPromise.then(() => {
      //       return offering.get('learnerGroups');
      //     })
      //     .then((groups) => {
      //       groups.pushObject('learnerGroup');
      //     });
      //
      //   const instructorsPromise = offeringPromise.then(() => {
      //       const instructors = offering.get('instructors');
      //       const defaultInstructors = learnerGroup.get('instructorUsers');
      //
      //       return hash({ instructors, defaultInstructors });
      //     })
      //     .then(({ instructors, defaultInstructors }) => {
      //       if (isPresent(defaultInstructors)) {
      //         instructors.pushObjects(defaultInstructors);
      //       }
      //     });
      //
      //   return all([ learnerGroupsPromise, instructorsPromise ]).then(() => {
      //     return offering.save();
      //   })
      // });
      //
      // all(promises).finally(() => {
      //   this.set('saving', false);
      // });
    },

    toggleEditor() {
      this.set('offeringEditorOn', !this.get('offeringEditorOn'));
    },

    closeEditor() {
      this.set('offeringEditorOn', false);
    }
  }
});
<|endoftext|>