<commit_before>/*
 * ==================================================
 * Responses
 * ==================================================
 *
 * Collects responses from web, mobile, and paper forms.
 *
 * Data structure for paper-originating response:
 * responses: [
 *   { parcel_id: "3728049",
 *     responses: {"type":3,"business":1},
 *     id: "cf1fa4c0-8e9e-11e1-8159-e53f8dbd1a6a",
 *     survey: "e458a930-8e27-11e1-b08f-37bd5a7df741",
 *     source : {type : "paper", scan: "", form: ""}
 *   }
 * ]
 * Data structure for mobile-originating response:
 * responses: [
 *   { parcel_id: "3728049",
 *     responses: {"type":3,"business":1},
 *     id: "cf1fa4c0-8e9e-11e1-8159-e53f8dbd1a6a",
 *     survey: "e458a930-8e27-11e1-b08f-37bd5a7df741",
 *     source : {type : "mobile"}
 *   }
 * ]
 *
 */

var util = require('./util');

module.exports = {
  setup: setup
};

var handleError = util.handleError;
var isArray = util.isArray;

/*
 * app: express server
 * db: mongodb database
 * idgen: unique ID generator
 * collectionName: name of responses collection
 */
function setup(app, db, idgen, collectionName) {
  function getCollection(cb) {
    return db.collection(collectionName, cb);
  }

  //function ensureGeoIndex() {
  //  console.log(db);
  //  db.RESPONSES.ensureIndex({geo_info: {centroid: "2d"}});
  //}
  //
  // Get all responses for a survey.
  // GET http://localhost:3000/surveys/{SURVEY ID}/responses
  // GET http://localhost:3000/surveys/1/responses
  app.get('/surveys/:sid/responses', function(req, response) {
    var surveyid = req.params.sid;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send();
          return;
        }
        cursor.toArray(function(err, items) {
          response.send({responses: items});
        });
      });
    });
  });

  // Get all responses for a specific parcel.
  // TODO: At some point, parcel should become a generic geographic object ID.
  // GET http://localhost:3000/surveys/{SURVEY ID}/parcels/{PARCEL ID}/responses
  // GET http://localhost:3000/surveys/1/parcels/3728048/responses
  app.get('/surveys/:sid/parcels/:parcel_id/responses', function(req, response) {
    var surveyid = req.params.sid;
    var parcel_id = req.params.parcel_id;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid, 'parcel_id': parcel_id}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send();
          return;
        }
        cursor.toArray(function(err, items) {
          response.send({responses: items});
        });
      });
    });
  });

  // Get a response for a survey.
  // GET http://localhost:3000/surveys/{SURVEY ID}/responses/{RESPONSE ID}
  // GET http://localhost:3000/surveys/1/responses/2ec140e0-827f-11e1-83d8-bf682a6ee038
  app.get('/surveys/:sid/responses/:rid', function(req, response) {
    var surveyid = req.params.sid;
    var responseid = req.params.rid;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid, 'id': responseid}, function(err, cursor) {
        if (handleError(err, response)) return;

        cursor.toArray(function(err, items) {
          if (items.length > 1) {
            console.log('!!! WARNING: There should only be one response with a given id attached to a survey.');
            console.log('!!! Found ' + items.length);
            console.log('!!! Responses: ' + JSON.stringify(items));
          }
          if (items.length > 0) {
            response.send({response: items[0]});
          } else {
            response.send({});
          }
        });
      });
    });
  });

  // Delete a response from a survey
  // DELETE http://localhost:3000/surveys/{SURVEY ID}/responses/{RESPONSE ID}
  app.del('/surveys/:sid/responses/:rid', function(req, response) {
    var survey = req.params.sid;
    var id = req.params.rid;
    console.log('Removing response ' + id + ' from the database.');
    getCollection(function(err, collection) {
      collection.remove({survey: survey, id: id}, {safe: true}, function(error, count) {
        if (error != null) {
          console.log('Error removing response ' + id + ' for survey ' + survey + ' from the response collection: ' + err.message);
          response.send();
        } else {
          if (count != 1) {
            console.log('!!! We should have removed exactly 1 entry. Instead we removed ' + count + ' entries.');
          }
          response.send({count: count});
        }
      });
    });
  });

  // Add responses for a survey.
  // POST http://localhost:3000/surveys/{SURVEY ID}/reponses
  // POST http://localhost:3000/surveys/1/reponses
  // Expects data in the format:
  // responses: [
  //  { parcels: [ {parcel_id: '10', responses: {'Q0': 0, 'Q1': 3}} ]}, ...]
  app.post('/surveys/:sid/responses', function(req, response) {
    var resps = req.body.responses;
    var total = resps.length;

    console.log(resps);
    console.log('Adding ' + total + ' responses to the database.');

    var count = 0;
    getCollection(function(err, collection) {
      var surveyid = req.params.sid;

      // Iterate over each survey response we received.
      resps.forEach(function(resp) {

        // Add metadata to the survey response
        var id = idgen();
        resp.id = id;
        resp.survey = surveyid;
        resp.created = new Date();

        // check if there is a centroid. if yes, make sure the values are floats
        // TODO: abstract into a testable function.
        var centroid = resp["geo_info"]["centroid"];
        if (centroid !== undefined) {
          resp["geo_info"]["centroid"][0] = parseFloat(centroid[0]);
          resp["geo_info"]["centroid"][1] = parseFloat(centroid[1]);
        };

        // Add response to database.
        collection.insert(resp, function() {
          console.log(resp);
          // Check if we've added all of them.
          if (++count == total) {
            console.log('Created ' + total + 'items. Returning.');
            response.send({responses: resps}, 201);
          }
        });
      });
    });
  });

  // Delete all responses for a survey.
  // This is maintainence functionality. Regular clients should not delete forms.
  // DELETE http://localhost:3000/surveys/{SURVEY ID}/reponses
  // DELETE http://localhost:3000/surveys/1/reponses
  app.del('/surveys/:sid/responses', function(req, response) {
    var survey = req.params.sid;
    console.log('!!! Deleting responses for survey ' + survey + ' from the database.');
    getCollection(function(err, collection) {
      collection.remove({survey: survey}, {safe: true}, function(error, count) {
        if (error != null) {
          console.log('Error removing responses for survey ' + survey + ' from the response collection: ' + err.message);
          response.send();
        } else {
          response.send({count: count});
        }
      });
    });
  });

  // Get all responses in a bounding box
  // GET http://localhost:3000/surveys/{SURVEY ID}/reponses/in/lower-left lat,lower-left lng, upper-right lat, upper-right lng
  // GET http://localhost:3000/surveys/{SURVEY ID}/reponses/in/1,2,3,4
  app.get('/surveys/:sid/responses/in/:bounds', function(req, response) {
    var surveyid = req.params.sid;
    var bounds = req.params.bounds;
    var coords = bounds.split(",");
    if (coords.length != 4) {
      // There need to be four points.
      response.send({}, 400);
    };
    for (var i = -1, ln = coords.length; ++i < ln;) {
      coords[i] = parseFloat(coords[i]);
    };

    var bbox = [[coords[0], coords[1]], [coords[2],  coords[3]]];
    query = {'survey': surveyid, 'geo_info.centroid': {"$within": { "$box": bbox}}};
    console.log("Bounds query ====================");
    console.log(query['geo_info.centroid']['$within']["$box"]);

    getCollection(function(err, collection) {
      collection.find(query, function(err, cursor) {
        if (handleError(err, response)) return;

        cursor.toArray(function(err, items) {
          console.log("Bounds results =========");
          console.log(items);
          if (items.length > 0) {
            response.send({"responses":items});
          } else {
            response.send({});
          }
        });
      });
    });
  });

  function commasep(row, headers, headerCount) {
    var arr = [];
    for (var i = 0; i < row.length; i++) {
      if (headerCount[headers[i]] === 1) {
        // No multiple-choice for this column
        arr.push(row[i]);
      } else {
        var len;
        if (!isArray(row[i])) {
          arr.push(row[i]);
          len = 1;
        } else {
          len = row[i].length;
          for (var j = 0; j < len; j++) {
            arr.push(row[i][j]);
          }
        }
        // Padding for multiple-choice situations
        for (var h = 0; h < headerCount[headers[i]] - len; h++) {
          arr.push(null);
        }
      }
    }

    return arr.join(',');
  }

  // Return response data as CSV
  // GET http://localhost:5000/surveys/{SURVEY ID}/csv
  app.get('/surveys/:sid/csv', function(req, response) {
    var sid = req.params.sid;
    getCollection(function(err, collection) {
      collection.find({'survey': sid}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send(500);
          return;
        }
        cursor.toArray(function(err, items) {
          var headers = ['parcel_id', 'source'];
          var headerIndices = {};
          var headerCount = {};
          var i;
          for (i = 0; i < headers.length; i++) {
            headerIndices[headers[i]] = i;
            headerCount[headers[i]] = 1;
          }
          var rows = [];
          var len = items.length;
          // Iterate over each response
          for (i = 0; i < len; i++) {
            var responses = items[i].responses;

            // Add context entries (parcel ID, source type)
            var row = [items[i].parcel_id, items[i].source.type];

            for (var resp in responses) {
              if (responses.hasOwnProperty(resp)) {
                // If we haven't encountered this column, track it.
                if (!headerIndices.hasOwnProperty(resp)) {
                  headerIndices[resp] = headers.length;
                  headerCount[resp] = 1;
                  headers.push(resp);
                  // Add an empty entry to each existing row, since they didn't
                  // have this column.
                  for (var j = 0; j < rows.length; j++) {
                    rows[j].push('');
                  }
                }
                var entry = responses[resp];
                // Check if we have multiple answers.
                if (isArray(entry)) {
                  if (entry.length > headerCount[resp]) {
                    headerCount[resp] = entry.length;
                  }
                }
                // Add the response to the CSV row.
                row[headerIndices[resp]] = responses[resp];
              }
            }
            // Add the CSV row.
            rows.push(row);
          }

          response.writeHead(200, {
            'Content-Type': 'text/csv'
          });
          // Turn each row into a CSV line
          response.write(commasep(headers, headers, headerCount));
          response.write('\n');
          for (i = 0; i < len; i++) {
            response.write(commasep(rows[i], headers, headerCount));
            response.write('\n');
          }
          response.end();
        });
      });
    });
  });

} // setup()
<commit_msg>Style changes for merge<commit_after>/*
 * ==================================================
 * Responses
 * ==================================================
 *
 * Collects responses from web, mobile, and paper forms.
 *
 * Data structure for paper-originating response:
 * responses: [
 *   { parcel_id: "3728049",
 *     responses: {"type":3,"business":1},
 *     id: "cf1fa4c0-8e9e-11e1-8159-e53f8dbd1a6a",
 *     survey: "e458a930-8e27-11e1-b08f-37bd5a7df741",
 *     source : {type : "paper", scan: "", form: ""}
 *   }
 * ]
 * Data structure for mobile-originating response:
 * responses: [
 *   { parcel_id: "3728049",
 *     responses: {"type":3,"business":1},
 *     id: "cf1fa4c0-8e9e-11e1-8159-e53f8dbd1a6a",
 *     survey: "e458a930-8e27-11e1-b08f-37bd5a7df741",
 *     source : {type : "mobile"}
 *   }
 * ]
 *
 */

var util = require('./util');

module.exports = {
  setup: setup
};

var handleError = util.handleError;
var isArray = util.isArray;

/*
 * app: express server
 * db: mongodb database
 * idgen: unique ID generator
 * collectionName: name of responses collection
 */
function setup(app, db, idgen, collectionName) {
  function getCollection(cb) {
    return db.collection(collectionName, cb);
  }

  // Get all responses for a survey.
  // GET http://localhost:3000/surveys/{SURVEY ID}/responses
  // GET http://localhost:3000/surveys/1/responses
  app.get('/surveys/:sid/responses', function(req, response) {
    var surveyid = req.params.sid;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send();
          return;
        }
        cursor.toArray(function(err, items) {
          response.send({responses: items});
        });
      });
    });
  });

  // Get all responses for a specific parcel.
  // TODO: At some point, parcel should become a generic geographic object ID.
  // GET http://localhost:3000/surveys/{SURVEY ID}/parcels/{PARCEL ID}/responses
  // GET http://localhost:3000/surveys/1/parcels/3728048/responses
  app.get('/surveys/:sid/parcels/:parcel_id/responses', function(req, response) {
    var surveyid = req.params.sid;
    var parcel_id = req.params.parcel_id;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid, 'parcel_id': parcel_id}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send();
          return;
        }
        cursor.toArray(function(err, items) {
          response.send({responses: items});
        });
      });
    });
  });

  // Get a response for a survey.
  // GET http://localhost:3000/surveys/{SURVEY ID}/responses/{RESPONSE ID}
  // GET http://localhost:3000/surveys/1/responses/2ec140e0-827f-11e1-83d8-bf682a6ee038
  app.get('/surveys/:sid/responses/:rid', function(req, response) {
    var surveyid = req.params.sid;
    var responseid = req.params.rid;
    getCollection(function(err, collection) {
      collection.find({'survey': surveyid, 'id': responseid}, function(err, cursor) {
        if (handleError(err, response)) return;

        cursor.toArray(function(err, items) {
          if (items.length > 1) {
            console.log('!!! WARNING: There should only be one response with a given id attached to a survey.');
            console.log('!!! Found ' + items.length);
            console.log('!!! Responses: ' + JSON.stringify(items));
          }
          if (items.length > 0) {
            response.send({response: items[0]});
          } else {
            response.send({});
          }
        });
      });
    });
  });

  // Delete a response from a survey
  // DELETE http://localhost:3000/surveys/{SURVEY ID}/responses/{RESPONSE ID}
  app.del('/surveys/:sid/responses/:rid', function(req, response) {
    var survey = req.params.sid;
    var id = req.params.rid;
    console.log('Removing response ' + id + ' from the database.');
    getCollection(function(err, collection) {
      collection.remove({survey: survey, id: id}, {safe: true}, function(error, count) {
        if (error != null) {
          console.log('Error removing response ' + id + ' for survey ' + survey + ' from the response collection: ' + err.message);
          response.send();
        } else {
          if (count != 1) {
            console.log('!!! We should have removed exactly 1 entry. Instead we removed ' + count + ' entries.');
          }
          response.send({count: count});
        }
      });
    });
  });

  // Add responses for a survey.
  // POST http://localhost:3000/surveys/{SURVEY ID}/reponses
  // POST http://localhost:3000/surveys/1/reponses
  // Expects data in the format:
  // responses: [
  //  { parcels: [ {parcel_id: '10', responses: {'Q0': 0, 'Q1': 3}} ]}, ...]
  app.post('/surveys/:sid/responses', function(req, response) {
    var resps = req.body.responses;
    var total = resps.length;

    console.log(resps);
    console.log('Adding ' + total + ' responses to the database.');

    var count = 0;
    getCollection(function(err, collection) {
      var surveyid = req.params.sid;

      // Iterate over each survey response we received.
      resps.forEach(function(resp) {

        // Add metadata to the survey response
        var id = idgen();
        resp.id = id;
        resp.survey = surveyid;
        resp.created = new Date();

        // check if there is a centroid. if yes, make sure the values are floats
        // TODO: abstract into a testable function.
        var centroid = resp["geo_info"]["centroid"];
        if (centroid !== undefined) {
          centroid[0] = parseFloat(centroid[0]);
          centroid[1] = parseFloat(centroid[1]);
        };

        // Add response to database.
        collection.insert(resp, function() {
          console.log(resp);
          // Check if we've added all of them.
          if (++count == total) {
            console.log('Created ' + total + 'items. Returning.');
            response.send({responses: resps}, 201);
          }
        });
      });
    });
  });

  // Delete all responses for a survey.
  // This is maintainence functionality. Regular clients should not delete forms.
  // DELETE http://localhost:3000/surveys/{SURVEY ID}/reponses
  // DELETE http://localhost:3000/surveys/1/reponses
  app.del('/surveys/:sid/responses', function(req, response) {
    var survey = req.params.sid;
    console.log('!!! Deleting responses for survey ' + survey + ' from the database.');
    getCollection(function(err, collection) {
      collection.remove({survey: survey}, {safe: true}, function(error, count) {
        if (error != null) {
          console.log('Error removing responses for survey ' + survey + ' from the response collection: ' + err.message);
          response.send();
        } else {
          response.send({count: count});
        }
      });
    });
  });

  // Get all responses in a bounding box
  // GET http://localhost:3000/surveys/{SURVEY ID}/reponses/in/lower-left lat,lower-left lng, upper-right lat, upper-right lng
  // GET http://localhost:3000/surveys/{SURVEY ID}/reponses/in/1,2,3,4
  app.get('/surveys/:sid/responses/in/:bounds', function(req, response) {
    var surveyid = req.params.sid;
    var bounds = req.params.bounds;
    var coords = bounds.split(",");
    if (coords.length != 4) {
      // There need to be four points.
      response.send(s400);
    };
    for (var i = -1, ln = coords.length; ++i < ln;) {
      coords[i] = parseFloat(coords[i]);
    };

    var bbox = [[coords[0], coords[1]], [coords[2],  coords[3]]];
    query = {'survey': surveyid, 'geo_info.centroid': {"$within": { "$box": bbox}}};
    console.log("Bounds query ====================");
    console.log(query['geo_info.centroid']['$within']["$box"]);

    getCollection(function(err, collection) {
      collection.find(query, function(err, cursor) {
        if (handleError(err, response)) return;

        cursor.toArray(function(err, items) {
          console.log("Bounds results =========");
          console.log(items);
          if (items.length > 0) {
            response.send({"responses":items});
          } else {
            response.send({});
          }
        });
      });
    });
  });

  function commasep(row, headers, headerCount) {
    var arr = [];
    for (var i = 0; i < row.length; i++) {
      if (headerCount[headers[i]] === 1) {
        // No multiple-choice for this column
        arr.push(row[i]);
      } else {
        var len;
        if (!isArray(row[i])) {
          arr.push(row[i]);
          len = 1;
        } else {
          len = row[i].length;
          for (var j = 0; j < len; j++) {
            arr.push(row[i][j]);
          }
        }
        // Padding for multiple-choice situations
        for (var h = 0; h < headerCount[headers[i]] - len; h++) {
          arr.push(null);
        }
      }
    }

    return arr.join(',');
  }

  // Return response data as CSV
  // GET http://localhost:5000/surveys/{SURVEY ID}/csv
  app.get('/surveys/:sid/csv', function(req, response) {
    var sid = req.params.sid;
    getCollection(function(err, collection) {
      collection.find({'survey': sid}, function(err, cursor) {
        if (err != null) {
          console.log('Error retrieving responses for survey ' + surveyid + ': ' + err.message);
          response.send(500);
          return;
        }
        cursor.toArray(function(err, items) {
          var headers = ['parcel_id', 'source'];
          var headerIndices = {};
          var headerCount = {};
          var i;
          for (i = 0; i < headers.length; i++) {
            headerIndices[headers[i]] = i;
            headerCount[headers[i]] = 1;
          }
          var rows = [];
          var len = items.length;
          // Iterate over each response
          for (i = 0; i < len; i++) {
            var responses = items[i].responses;

            // Add context entries (parcel ID, source type)
            var row = [items[i].parcel_id, items[i].source.type];

            for (var resp in responses) {
              if (responses.hasOwnProperty(resp)) {
                // If we haven't encountered this column, track it.
                if (!headerIndices.hasOwnProperty(resp)) {
                  headerIndices[resp] = headers.length;
                  headerCount[resp] = 1;
                  headers.push(resp);
                  // Add an empty entry to each existing row, since they didn't
                  // have this column.
                  for (var j = 0; j < rows.length; j++) {
                    rows[j].push('');
                  }
                }
                var entry = responses[resp];
                // Check if we have multiple answers.
                if (isArray(entry)) {
                  if (entry.length > headerCount[resp]) {
                    headerCount[resp] = entry.length;
                  }
                }
                // Add the response to the CSV row.
                row[headerIndices[resp]] = responses[resp];
              }
            }
            // Add the CSV row.
            rows.push(row);
          }

          response.writeHead(200, {
            'Content-Type': 'text/csv'
          });
          // Turn each row into a CSV line
          response.write(commasep(headers, headers, headerCount));
          response.write('\n');
          for (i = 0; i < len; i++) {
            response.write(commasep(rows[i], headers, headerCount));
            response.write('\n');
          }
          response.end();
        });
      });
    });
  });

} // setup()
<|endoftext|>


<commit_before>Meteor = {
  isClient: false,
  isServer: true
};

Meteor.settings = {};
if (process.env.METEOR_SETTINGS) {
  try {
    Meteor.settings = JSON.parse(process.env.METEOR_SETTINGS);
  } catch (e) {
    throw new Error("Settings are not valid JSON");
  }
} else if ( process.env.APP_CONFIG) {
  // put settings from the app configuration in the settings.  Don't depend on
  // the Galaxy package for now, to avoid silly loops.
 try {
    Meteor.settings = JSON.parse(process.env.APP_CONFIG).settings || {};
  } catch (e) {
    throw new Error("Settings are not valid JSON");
  }
}
// Push a subset of settings to the client.
if (Meteor.settings && Meteor.settings.public &&
    typeof __meteor_runtime_config__ === "object") {
  __meteor_runtime_config__.PUBLIC_SETTINGS = Meteor.settings.public;
}
<commit_msg>Changes to how Meteor.settings is filled on Galaxy<commit_after>Meteor = {
  isClient: false,
  isServer: true
};

Meteor.settings = {};

if (process.env.APP_CONFIG) {
  // put settings from the app configuration in the settings.  Don't depend on
  // the Galaxy package for now, to avoid silly loops.
 try {
   var appConfig = JSON.parse(process.env.APP_CONFIG);
   if (!appConfig.settings) {
     Meteor.settings = {};
   } else if (typeof appConfig.settings === "string") {
     Meteor.settings = JSON.parse(appConfig.settings);
   } else {
     // Old versions of Galaxy may store settings in MongoDB as objects. Newer
     // versions store it as strings (so that we aren't restricted to
     // MongoDB-compatible objects). This line makes it work on older Galaxies.
     // XXX delete this eventually
     Meteor.settings = appConfig.settings;
   }
  } catch (e) {
    throw new Error("Settings from app config are not valid JSON");
  }
} else if (process.env.METEOR_SETTINGS) {
  try {
    Meteor.settings = JSON.parse(process.env.METEOR_SETTINGS);
  } catch (e) {
    throw new Error("Settings are not valid JSON");
  }
}

// Push a subset of settings to the client.
if (Meteor.settings && Meteor.settings.public &&
    typeof __meteor_runtime_config__ === "object") {
  __meteor_runtime_config__.PUBLIC_SETTINGS = Meteor.settings.public;
}
<|endoftext|>


<commit_before>import set from 'lodash/set';
import get from 'lodash/get';
import has from 'lodash/has';
import Ajv from 'ajv';
import ajvKeywords from 'ajv-keywords';
import invariant from 'invariant';
import { MongoClient, ObjectID } from 'mongodb';
import path from 'path';
import glob from 'glob';
import { Module } from 'oors';
import Repository from './libs/Repository';
import * as helpers from './libs/helpers';
import idValidator from './libs/idValidator';
import * as decorators from './decorators';
import MigrationRepository from './repositories/Migration';
import Seeder from './libs/Seeder';
import withLogger from './decorators/withLogger';
import withTimestamps from './decorators/withTimestamps';
import Migration from './libs/Migration';
import Migrator from './libs/Migrator';

class MongoDB extends Module {
  static schema = {
    type: 'object',
    properties: {
      connections: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            name: {
              type: 'string',
            },
            database: {
              type: 'string',
            },
            url: {
              type: 'string',
            },
            options: {
              type: 'object',
              default: {},
            },
          },
          required: ['name', 'url'],
        },
        minItems: 1,
      },
      defaultConnection: {
        type: 'string',
      },
      migration: {
        type: 'object',
        properties: {
          isEnabled: {
            type: 'boolean',
            default: false,
          },
          dir: {
            type: 'string',
          },
        },
        default: {},
      },
      logQueries: {
        type: 'boolean',
        default: true,
      },
      addTimestamps: {
        type: 'boolean',
        default: true,
      },
      seeding: {
        type: 'object',
        properties: {
          isEnabled: {
            type: 'boolean',
            default: false,
          },
        },
        default: {},
      },
    },
    required: ['connections'],
  };

  name = 'oors.mongodb';

  connections = {};

  repositories = {};

  hooks = {
    'oors.graphql.buildContext': ({ context }) => {
      const { fromMongo, fromMongoCursor, fromMongoArray, toMongo } = helpers;

      if (context.ajv) {
        context.ajv.addKeyword('isId', idValidator);
      }

      Object.assign(context, {
        fromMongo,
        fromMongoCursor,
        fromMongoArray,
        toMongo,
        getRepository: this.getRepository,
        toOjectId: this.toOjectId,
      });
    },
    shutdown: () => this.closeConnection(),
  };

  initialize({ connections, defaultConnection }) {
    this.defaultConnectionName = defaultConnection || connections[0].name;

    const names = connections.map(({ name }) => name);

    if (!names.includes(this.defaultConnectionName)) {
      throw new Error(
        `Default connection name - "(${
          this.defaultConnectionName
        })" - can't be found through the list of available connections (${names})`,
      );
    }
  }

  async setup({ connections, logQueries, addTimestamps }) {
    await Promise.all(connections.map(this.createConnection));

    this.setupValidator();
    this.setupMigration();
    await this.setupSeeding();

    this.onModule(this.name, 'repository', ({ repository }) => {
      if (logQueries) {
        withLogger()(repository);
      }

      if (addTimestamps) {
        withTimestamps()(repository);
      }
    });

    this.exportProperties([
      'createConnection',
      'closeConnection',
      'getConnection',
      'getConnectionDb',
      'createStore',
      'createRepository',
      'bindRepository',
      'bindRepositories',
      'addRepository',
      'getRepository',
      'migrate',
      'ajv',
      'toOjectId',
      'transaction',
      'backup',
    ]);
  }

  setupValidator() {
    this.ajv = new Ajv({
      allErrors: true,
      verbose: true,
      async: 'es7',
      useDefaults: true,
    });

    ajvKeywords(this.ajv, 'instanceof');

    this.ajv.addKeyword('isId', idValidator);
  }

  setupMigration() {
    if (!this.getConfig('migration.isEnabled')) {
      return;
    }

    const migrationRepository = this.addRepository('Migration', new MigrationRepository());

    this.migrator = new Migrator({
      migrationsDir: this.getConfig('migration.dir'),
      context: {
        app: this.app,
        db: this.getConnectionDb(),
      },
      MigrationRepository: migrationRepository,
      transaction: this.transaction,
      backup: this.backup,
    });
  }

  async setupSeeding() {
    if (!this.getConfig('seeding.isEnabled')) {
      return;
    }

    const seeder = new Seeder();
    const seeds = {};

    await Promise.all([
      this.runHook('configureSeeder', () => {}, {
        seeder,
        getRepository: this.getRepository,
      }),
      this.runHook('loadSeedData', () => {}, {
        seeds,
      }),
    ]);

    if (Object.keys(seeds).length) {
      await this.seed(seeds);
    }

    this.export({
      seeder,
    });

    this.exportProperties(['seed', 'seeds']);
  }

  createRepository = ({ methods = {}, connectionName, ...options }) => {
    const repository = new Repository(options);

    Object.keys(methods).forEach(methodName => {
      repository[methodName] = methods[methodName].bind(repository);
    });

    this.bindRepository(repository, connectionName);

    return repository;
  };

  bindRepositories = (repositories, connectionName) =>
    repositories.map(repository => this.bindRepository(repository, connectionName));

  bindRepository = (repository, connectionName) => {
    invariant(
      repository.collectionName,
      `Missing repository collection name - ${repository.constructor.name}!`,
    );

    Object.assign(repository, {
      collection: !repository.hasCollection()
        ? this.getConnectionDb(connectionName).collection(repository.collectionName)
        : repository.collection,
      ajv: this.ajv,
      validate:
        repository.validate ||
        (repository.schema ? this.ajv.compile(repository.schema) : () => true),
      getRepository: this.getRepository,
    });

    repository.configure({
      getRepository: this.getRepository,
    });

    return repository;
  };

  addRepository = (key, repository, options = {}) => {
    const payload = {
      key,
      repository,
      options,
    };

    this.emit('repository', payload);

    set(
      this.repositories,
      payload.key,
      this.bindRepository(payload.repository, options.connectionName),
    );

    return this.getRepository(payload.key);
  };

  getRepository = key => {
    if (!has(this.repositories, key)) {
      throw new Error(`Unable to find "${key}" repository!`);
    }

    return get(this.repositories, key);
  };

  createConnection = async ({ name, url, options }) => {
    this.connections[name] = await MongoClient.connect(
      url,
      options,
    );
    return this.connections[name];
  };

  getConnectionDb = (name = this.defaultConnectionName) => {
    const connection = this.getConnection(name);
    const { database, url } = this.getConfig('connections').find(
      ({ name: _name }) => _name === name,
    );
    return connection.db(database || url.substr(url.lastIndexOf('/') + 1));
  };

  getConnection = name => {
    if (!name) {
      return this.connections[this.defaultConnectionName];
    }

    if (!this.connections[name]) {
      throw new Error(`Unknown connection name - "${name}"!`);
    }

    return this.connections[name];
  };

  closeConnection = name => this.getConnection(name).close();

  migrate = async () => {
    if (!this.getConfig('migration.isEnabled')) {
      throw new Error('Migrations are not enabled!');
    }

    const migrationsDir = this.getConfig('migration.dir');
    if (!migrationsDir) {
      throw new Error(
        `Missing migrations directory! Please provide a "migrationsDir" configuration
        directive where your migration files are located.`,
      );
    }

    this.emit('migration:before');

    const db = this.getConnectionDb();

    const migrationFiles = await new Promise((resolve, reject) => {
      glob(path.join(migrationsDir, '*.js'), (err, files) => {
        if (err) {
          reject(err);
        } else {
          resolve(files);
        }
      });
    });

    if (!migrationFiles.length) {
      throw new Error(
        `No migration files have been found in the migrations directory ("${migrationsDir}")!`,
      );
    }

    const lastDbMigration = await this.getRepository('Migration').findOne({
      options: {
        sort: [['timestamp', '-1']],
      },
    });

    const lastDbMigrationTimestamp = lastDbMigration ? lastDbMigration.timestamp : 0;

    return migrationFiles
      .filter(file => helpers.getTimestampFromMigrationFile(file) > lastDbMigrationTimestamp)
      .reduce((promise, file) => {
        const timestamp = helpers.getTimestampFromMigrationFile(file);
        const MigrationClass = require(file).default; // eslint-disable-line import/no-dynamic-require, global-require
        const migration = new MigrationClass(this.app, db);

        return promise.then(() =>
          migration.up().then(() =>
            this.getRepository('Migration').createOne({
              timestamp,
              name: migration.name,
            }),
          ),
        );
      }, Promise.resolve());
  };

  seed = data => this.get('seeder').load(data);

  toOjectId = value => new ObjectID(value);

  transaction = async (cb, options = {}, connectionName) => {
    const connection = this.getConnectionDb(connectionName);
    const session = connection.startSession();
    session.startTransaction(options);
    try {
      await cb();
      await session.commitTransaction();
      session.endSession();
    } catch (error) {
      await session.abortTransaction();
      session.endSession();
      throw error;
    }
  };

  // eslint-disable-next-line
  backup = connectionName => {
    // https://github.com/theycallmeswift/node-mongodb-s3-backup
    // https://dzone.com/articles/auto-backup-mongodb-database-with-nodejs-on-server-1
  };
}

export { MongoDB as default, Repository, helpers, decorators, Migration };
<commit_msg>[oors-mongodb] check if replica set is available<commit_after>import set from 'lodash/set';
import get from 'lodash/get';
import has from 'lodash/has';
import Ajv from 'ajv';
import ajvKeywords from 'ajv-keywords';
import invariant from 'invariant';
import { MongoClient, ObjectID } from 'mongodb';
import path from 'path';
import glob from 'glob';
import { Module } from 'oors';
import Repository from './libs/Repository';
import * as helpers from './libs/helpers';
import idValidator from './libs/idValidator';
import * as decorators from './decorators';
import MigrationRepository from './repositories/Migration';
import Seeder from './libs/Seeder';
import withLogger from './decorators/withLogger';
import withTimestamps from './decorators/withTimestamps';
import Migration from './libs/Migration';
import Migrator from './libs/Migrator';

class MongoDB extends Module {
  static schema = {
    type: 'object',
    properties: {
      connections: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            name: {
              type: 'string',
            },
            database: {
              type: 'string',
            },
            url: {
              type: 'string',
            },
            options: {
              type: 'object',
              default: {},
            },
          },
          required: ['name', 'url'],
        },
        minItems: 1,
      },
      defaultConnection: {
        type: 'string',
      },
      migration: {
        type: 'object',
        properties: {
          isEnabled: {
            type: 'boolean',
            default: false,
          },
          dir: {
            type: 'string',
          },
        },
        default: {},
      },
      logQueries: {
        type: 'boolean',
        default: true,
      },
      addTimestamps: {
        type: 'boolean',
        default: true,
      },
      seeding: {
        type: 'object',
        properties: {
          isEnabled: {
            type: 'boolean',
            default: false,
          },
        },
        default: {},
      },
    },
    required: ['connections'],
  };

  name = 'oors.mongodb';

  connections = {};

  repositories = {};

  hooks = {
    'oors.graphql.buildContext': ({ context }) => {
      const { fromMongo, fromMongoCursor, fromMongoArray, toMongo } = helpers;

      if (context.ajv) {
        context.ajv.addKeyword('isId', idValidator);
      }

      Object.assign(context, {
        fromMongo,
        fromMongoCursor,
        fromMongoArray,
        toMongo,
        getRepository: this.getRepository,
        toOjectId: this.toOjectId,
      });
    },
    shutdown: () => this.closeConnection(),
  };

  initialize({ connections, defaultConnection }) {
    this.defaultConnectionName = defaultConnection || connections[0].name;

    const names = connections.map(({ name }) => name);

    if (!names.includes(this.defaultConnectionName)) {
      throw new Error(
        `Default connection name - "(${
          this.defaultConnectionName
        })" - can't be found through the list of available connections (${names})`,
      );
    }
  }

  async setup({ connections, logQueries, addTimestamps }) {
    await Promise.all(connections.map(this.createConnection));

    this.setupValidator();
    this.setupMigration();
    await this.setupSeeding();

    this.onModule(this.name, 'repository', ({ repository }) => {
      if (logQueries) {
        withLogger()(repository);
      }

      if (addTimestamps) {
        withTimestamps()(repository);
      }
    });

    this.exportProperties([
      'createConnection',
      'closeConnection',
      'getConnection',
      'getConnectionDb',
      'createStore',
      'createRepository',
      'bindRepository',
      'bindRepositories',
      'addRepository',
      'getRepository',
      'migrate',
      'ajv',
      'toOjectId',
      'transaction',
      'backup',
    ]);
  }

  setupValidator() {
    this.ajv = new Ajv({
      allErrors: true,
      verbose: true,
      async: 'es7',
      useDefaults: true,
    });

    ajvKeywords(this.ajv, 'instanceof');

    this.ajv.addKeyword('isId', idValidator);
  }

  setupMigration() {
    if (!this.getConfig('migration.isEnabled')) {
      return;
    }

    const migrationRepository = this.addRepository('Migration', new MigrationRepository());

    this.migrator = new Migrator({
      migrationsDir: this.getConfig('migration.dir'),
      context: {
        app: this.app,
        db: this.getConnectionDb(),
      },
      MigrationRepository: migrationRepository,
      transaction: this.transaction,
      backup: this.backup,
    });
  }

  async setupSeeding() {
    if (!this.getConfig('seeding.isEnabled')) {
      return;
    }

    const seeder = new Seeder();
    const seeds = {};

    await Promise.all([
      this.runHook('configureSeeder', () => {}, {
        seeder,
        getRepository: this.getRepository,
      }),
      this.runHook('loadSeedData', () => {}, {
        seeds,
      }),
    ]);

    if (Object.keys(seeds).length) {
      await this.seed(seeds);
    }

    this.export({
      seeder,
    });

    this.exportProperties(['seed', 'seeds']);
  }

  createRepository = ({ methods = {}, connectionName, ...options }) => {
    const repository = new Repository(options);

    Object.keys(methods).forEach(methodName => {
      repository[methodName] = methods[methodName].bind(repository);
    });

    this.bindRepository(repository, connectionName);

    return repository;
  };

  bindRepositories = (repositories, connectionName) =>
    repositories.map(repository => this.bindRepository(repository, connectionName));

  bindRepository = (repository, connectionName) => {
    invariant(
      repository.collectionName,
      `Missing repository collection name - ${repository.constructor.name}!`,
    );

    Object.assign(repository, {
      collection: !repository.hasCollection()
        ? this.getConnectionDb(connectionName).collection(repository.collectionName)
        : repository.collection,
      ajv: this.ajv,
      validate:
        repository.validate ||
        (repository.schema ? this.ajv.compile(repository.schema) : () => true),
      getRepository: this.getRepository,
    });

    repository.configure({
      getRepository: this.getRepository,
    });

    return repository;
  };

  addRepository = (key, repository, options = {}) => {
    const payload = {
      key,
      repository,
      options,
    };

    this.emit('repository', payload);

    set(
      this.repositories,
      payload.key,
      this.bindRepository(payload.repository, options.connectionName),
    );

    return this.getRepository(payload.key);
  };

  getRepository = key => {
    if (!has(this.repositories, key)) {
      throw new Error(`Unable to find "${key}" repository!`);
    }

    return get(this.repositories, key);
  };

  createConnection = async ({ name, url, options }) => {
    this.connections[name] = await MongoClient.connect(
      url,
      options,
    );
    return this.connections[name];
  };

  getConnectionDb = (name = this.defaultConnectionName) => {
    const connection = this.getConnection(name);
    const { database, url } = this.getConfig('connections').find(
      ({ name: _name }) => _name === name,
    );
    return connection.db(database || url.substr(url.lastIndexOf('/') + 1));
  };

  getConnection = name => {
    if (!name) {
      return this.connections[this.defaultConnectionName];
    }

    if (!this.connections[name]) {
      throw new Error(`Unknown connection name - "${name}"!`);
    }

    return this.connections[name];
  };

  closeConnection = name => this.getConnection(name).close();

  migrate = async () => {
    if (!this.getConfig('migration.isEnabled')) {
      throw new Error('Migrations are not enabled!');
    }

    const migrationsDir = this.getConfig('migration.dir');
    if (!migrationsDir) {
      throw new Error(
        `Missing migrations directory! Please provide a "migrationsDir" configuration
        directive where your migration files are located.`,
      );
    }

    this.emit('migration:before');

    const db = this.getConnectionDb();

    const migrationFiles = await new Promise((resolve, reject) => {
      glob(path.join(migrationsDir, '*.js'), (err, files) => {
        if (err) {
          reject(err);
        } else {
          resolve(files);
        }
      });
    });

    if (!migrationFiles.length) {
      throw new Error(
        `No migration files have been found in the migrations directory ("${migrationsDir}")!`,
      );
    }

    const lastDbMigration = await this.getRepository('Migration').findOne({
      options: {
        sort: [['timestamp', '-1']],
      },
    });

    const lastDbMigrationTimestamp = lastDbMigration ? lastDbMigration.timestamp : 0;

    return migrationFiles
      .filter(file => helpers.getTimestampFromMigrationFile(file) > lastDbMigrationTimestamp)
      .reduce((promise, file) => {
        const timestamp = helpers.getTimestampFromMigrationFile(file);
        const MigrationClass = require(file).default; // eslint-disable-line import/no-dynamic-require, global-require
        const migration = new MigrationClass(this.app, db);

        return promise.then(() =>
          migration.up().then(() =>
            this.getRepository('Migration').createOne({
              timestamp,
              name: migration.name,
            }),
          ),
        );
      }, Promise.resolve());
  };

  seed = data => this.get('seeder').load(data);

  toOjectId = value => new ObjectID(value);

  isReplicaSet = async connectionName => {
    try {
      await this.getConnectionDb(connectionName)
        .admin()
        .replSetGetStatus();
      return true;
    } catch (error) {
      return false;
    }
  };

  transaction = async (cb, options = {}, connectionName) => {
    const connection = this.getConnectionDb(connectionName);
    const session = connection.startSession();
    session.startTransaction(options);
    try {
      await cb();
      await session.commitTransaction();
      session.endSession();
    } catch (error) {
      await session.abortTransaction();
      session.endSession();
      throw error;
    }
  };

  // eslint-disable-next-line
  backup = connectionName => {
    // https://github.com/theycallmeswift/node-mongodb-s3-backup
    // https://dzone.com/articles/auto-backup-mongodb-database-with-nodejs-on-server-1
  };
}

export { MongoDB as default, Repository, helpers, decorators, Migration };
<|endoftext|>


<commit_before>var PeriodicTable = function() {
  this.board = new Array;

  this.elements = new Object;
  for(var row = 0; row < 9; row++) {
    var tableRow = new Array;
    for(var col = 0; col < 18; col++) {
      if(PERIODIC_TABLE[row][col] != 0) {
        this.elements[PERIODIC_TABLE[row][col]] = {
          x: col,
          y: row,
          show: false
        }
      }
      tableRow.push(0);
    }
    this.board.push(tableRow);
  }
}<commit_msg>Add function in PeriodicTable to show the given element<commit_after>var PeriodicTable = function() {
  this.board = new Array;

  this.elements = new Object;
  for(var row = 0; row < 9; row++) {
    var tableRow = new Array;
    for(var col = 0; col < 18; col++) {
      if(PERIODIC_TABLE[row][col] != 0) {
        this.elements[PERIODIC_TABLE[row][col]] = {
          x: col,
          y: row,
          show: false
        }
      }
      tableRow.push(0);
    }
    this.board.push(tableRow);
  }
}
PeriodicTable.prototype.showElement = function(atomicNum) {
  this.elements[atomicNum].show = true;
  this.board[elements[atomicNum].y][elements[atomicNum].x] = atomicNum;
}
<|endoftext|>


<commit_before>const path = require('path')
const _glob = require('glob')
const fs = require('fs').promises
const fetch = require('node-fetch')
const { promisify } = require('util')
const { Sema } = require('async-sema')
const { spawn, exec: execOrig } = require('child_process')

const glob = promisify(_glob)
const exec = promisify(execOrig)

const timings = []
const NUM_RETRIES = 2
const DEFAULT_CONCURRENCY = 2
const RESULTS_EXT = `.results.json`
const isTestJob = !!process.env.NEXT_TEST_JOB
const TIMINGS_API = `https://next-timings.jjsweb.site/api/timings`

const UNIT_TEST_EXT = '.unit.test.js'
const DEV_TEST_EXT = '.dev.test.js'
const PROD_TEST_EXT = '.prod.test.js'

const NON_CONCURRENT_TESTS = ['test/integration/basic/test/index.test.js']

// which types we have configured to run separate
const configuredTestTypes = [UNIT_TEST_EXT]

async function main() {
  let concurrencyIdx = process.argv.indexOf('-c')
  const concurrency =
    parseInt(process.argv[concurrencyIdx + 1], 10) || DEFAULT_CONCURRENCY

  const outputTimings = process.argv.indexOf('--timings') !== -1
  const writeTimings = process.argv.indexOf('--write-timings') !== -1
  const isAzure = process.argv.indexOf('--azure') !== -1
  const groupIdx = process.argv.indexOf('-g')
  const groupArg = groupIdx !== -1 && process.argv[groupIdx + 1]

  const testTypeIdx = process.argv.indexOf('--type')
  const testType = process.argv[testTypeIdx + 1]

  let filterTestsBy

  switch (testType) {
    case 'unit':
      filterTestsBy = UNIT_TEST_EXT
      break
    case 'dev':
      filterTestsBy = DEV_TEST_EXT
      break
    case 'production':
      filterTestsBy = PROD_TEST_EXT
      break
    case 'all':
      filterTestsBy = 'none'
      break
    default:
      break
  }

  console.log('Running tests with concurrency:', concurrency)
  let tests = process.argv.filter((arg) => arg.endsWith('.test.js'))
  let prevTimings

  if (tests.length === 0) {
    tests = (
      await glob('**/*.test.js', {
        nodir: true,
        cwd: path.join(__dirname, 'test'),
      })
    ).filter((test) => {
      // only include the specified type
      if (filterTestsBy) {
        return filterTestsBy === 'none' ? true : test.endsWith(filterTestsBy)
        // include all except the separately configured types
      } else {
        return !configuredTestTypes.some((type) => test.endsWith(type))
      }
    })

    if (outputTimings && groupArg) {
      console.log('Fetching previous timings data')
      try {
        const timingsFile = path.join(__dirname, 'test-timings.json')
        try {
          prevTimings = JSON.parse(await fs.readFile(timingsFile, 'utf8'))
          console.log('Loaded test timings from disk successfully')
        } catch (_) {}

        if (!prevTimings) {
          const timingsRes = await fetch(
            `${TIMINGS_API}?which=${isAzure ? 'azure' : 'actions'}`
          )

          if (!timingsRes.ok) {
            throw new Error(`request status: ${timingsRes.status}`)
          }
          prevTimings = await timingsRes.json()
          console.log('Fetched previous timings data successfully')

          if (writeTimings) {
            await fs.writeFile(timingsFile, JSON.stringify(prevTimings))
            console.log('Wrote previous timings data to', timingsFile)
            process.exit(0)
          }
        }
      } catch (err) {
        console.log(`Failed to fetch timings data`, err)
      }
    }
  }

  let testNames = [
    ...new Set(
      tests.map((f) => {
        let name = `${f.replace(/\\/g, '/').replace(/\/test$/, '')}`
        if (!name.startsWith('test/')) name = `test/${name}`
        return name
      })
    ),
  ]

  if (groupArg) {
    const groupParts = groupArg.split('/')
    const groupPos = parseInt(groupParts[0], 10)
    const groupTotal = parseInt(groupParts[1], 10)
    const numPerGroup = Math.ceil(testNames.length / groupTotal)
    let offset = groupPos === 1 ? 0 : (groupPos - 1) * numPerGroup - 1
    // if there's an odd number of suites give the first group the extra
    if (testNames.length % 2 !== 0 && groupPos !== 1) offset++

    if (prevTimings) {
      const groups = [[]]
      const groupTimes = [0]

      for (const testName of testNames) {
        let smallestGroup = groupTimes[0]
        let smallestGroupIdx = 0

        // get the smallest group time to add current one to
        for (let i = 1; i < groupTotal; i++) {
          if (!groups[i]) {
            groups[i] = []
            groupTimes[i] = 0
          }

          const time = groupTimes[i]
          if (time < smallestGroup) {
            smallestGroup = time
            smallestGroupIdx = i
          }
        }
        groups[smallestGroupIdx].push(testName)
        groupTimes[smallestGroupIdx] += prevTimings[testName] || 1
      }

      const curGroupIdx = groupPos - 1
      testNames = groups[curGroupIdx]

      console.log(
        'Current group previous accumulated times:',
        Math.round(groupTimes[curGroupIdx]) + 's'
      )
    } else {
      testNames = testNames.splice(offset, numPerGroup)
    }
  }
  console.log('Running tests:', '\n', ...testNames.map((name) => `${name}\n`))

  const sema = new Sema(concurrency, { capacity: testNames.length })
  const jestPath = path.join(
    path.dirname(require.resolve('jest-cli/package.json')),
    'bin/jest.js'
  )
  const children = new Set()

  const runTest = (test = '', usePolling) =>
    new Promise((resolve, reject) => {
      const start = new Date().getTime()
      const child = spawn(
        'node',
        [
          jestPath,
          '--runInBand',
          '--forceExit',
          '--verbose',
          ...(isTestJob
            ? ['--json', `--outputFile=${test}${RESULTS_EXT}`]
            : []),
          test,
        ],
        {
          stdio: 'inherit',
          env: {
            JEST_RETRY_TIMES: 0,
            ...process.env,
            ...(isAzure
              ? {
                  HEADLESS: 'true',
                  __POST_PROCESS_MIDDLEWARE_TIME_BUDGET: '50',
                }
              : {}),
            ...(usePolling
              ? {
                  // Events can be finicky in CI. This switches to a more
                  // reliable polling method.
                  CHOKIDAR_USEPOLLING: 'true',
                  CHOKIDAR_INTERVAL: 500,
                }
              : {}),
          },
        }
      )
      children.add(child)
      child.on('exit', (code) => {
        children.delete(child)
        if (code) reject(new Error(`failed with code: ${code}`))
        resolve(new Date().getTime() - start)
      })
    })

  const nonConcurrentTestNames = []

  testNames = testNames.filter((testName) => {
    if (NON_CONCURRENT_TESTS.includes(testName)) {
      nonConcurrentTestNames.push(testName)
      return false
    }
    return true
  })

  // run non-concurrent test names separately and before
  // concurrent ones
  for (const test of nonConcurrentTestNames) {
    let passed = false

    for (let i = 0; i < NUM_RETRIES + 1; i++) {
      try {
        const time = await runTest(test, i > 0)
        timings.push({
          file: test,
          time,
        })
        passed = true
        break
      } catch (err) {
        if (i < NUM_RETRIES) {
          try {
            const testDir = path.dirname(path.join(__dirname, test))
            console.log('Cleaning test files at', testDir)
            await exec(`git clean -fdx "${testDir}"`)
            await exec(`git checkout "${testDir}"`)
          } catch (err) {}
        }
      }
    }
    if (!passed) {
      console.error(`${test} failed to pass within ${NUM_RETRIES} retries`)
      children.forEach((child) => child.kill())

      if (isTestJob) {
        try {
          const testsOutput = await fs.readFile(`${test}${RESULTS_EXT}`, 'utf8')
          console.log(
            `--test output start--`,
            testsOutput,
            `--test output end--`
          )
        } catch (err) {
          console.log(`Failed to load test output`, err)
        }
      }
      process.exit(1)
    }
  }

  await Promise.all(
    testNames.map(async (test) => {
      await sema.acquire()
      let passed = false

      for (let i = 0; i < NUM_RETRIES + 1; i++) {
        try {
          const time = await runTest(test, i > 0)
          timings.push({
            file: test,
            time,
          })
          passed = true
          break
        } catch (err) {
          if (i < NUM_RETRIES) {
            try {
              const testDir = path.dirname(path.join(__dirname, test))
              console.log('Cleaning test files at', testDir)
              await exec(`git clean -fdx "${testDir}"`)
              await exec(`git checkout "${testDir}"`)
            } catch (err) {}
          }
        }
      }
      if (!passed) {
        console.error(`${test} failed to pass within ${NUM_RETRIES} retries`)
        children.forEach((child) => child.kill())

        if (isTestJob) {
          try {
            const testsOutput = await fs.readFile(
              `${test}${RESULTS_EXT}`,
              'utf8'
            )
            console.log(
              `--test output start--`,
              testsOutput,
              `--test output end--`
            )
          } catch (err) {
            console.log(`Failed to load test output`, err)
          }
        }
        process.exit(1)
      }
      sema.release()
    })
  )

  if (outputTimings) {
    const curTimings = {}
    // let junitData = `<testsuites name="jest tests">`
    /*
      <testsuite name="/__tests__/bar.test.js" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-10-10T21:56:49" time="0.323">
        <testcase classname="bar-should be bar" name="bar-should be bar" time="0.004">
        </testcase>
      </testsuite>
    */

    for (const timing of timings) {
      const timeInSeconds = timing.time / 1000
      curTimings[timing.file] = timeInSeconds

      // junitData += `
      //   <testsuite name="${timing.file}" file="${
      //   timing.file
      // }" tests="1" errors="0" failures="0" skipped="0" timestamp="${new Date().toJSON()}" time="${timeInSeconds}">
      //     <testcase classname="tests suite should pass" name="${
      //       timing.file
      //     }" time="${timeInSeconds}"></testcase>
      //   </testsuite>
      // `
    }
    // junitData += `</testsuites>`
    // console.log('output timing data to junit.xml')

    if (prevTimings) {
      try {
        const timingsRes = await fetch(TIMINGS_API, {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
          },
          body: JSON.stringify({
            timings: curTimings,
            which: isAzure ? 'azure' : 'actions',
          }),
        })

        if (!timingsRes.ok) {
          throw new Error(`request status: ${timingsRes.status}`)
        }
        console.log(
          'Sent updated timings successfully',
          await timingsRes.json()
        )
      } catch (err) {
        console.log('Failed to update timings data', err)
      }
    }
  }
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})
<commit_msg>Run acceptance tests non-concurrently (#25861)<commit_after>const path = require('path')
const _glob = require('glob')
const fs = require('fs').promises
const fetch = require('node-fetch')
const { promisify } = require('util')
const { Sema } = require('async-sema')
const { spawn, exec: execOrig } = require('child_process')

const glob = promisify(_glob)
const exec = promisify(execOrig)

const timings = []
const NUM_RETRIES = 2
const DEFAULT_CONCURRENCY = 2
const RESULTS_EXT = `.results.json`
const isTestJob = !!process.env.NEXT_TEST_JOB
const TIMINGS_API = `https://next-timings.jjsweb.site/api/timings`

const UNIT_TEST_EXT = '.unit.test.js'
const DEV_TEST_EXT = '.dev.test.js'
const PROD_TEST_EXT = '.prod.test.js'

const NON_CONCURRENT_TESTS = [
  'test/integration/basic/test/index.test.js',
  'test/acceptance/ReactRefresh.dev.test.js',
  'test/acceptance/ReactRefreshLogBox.dev.test.js',
  'test/acceptance/ReactRefreshRegression.dev.test.js',
  'test/acceptance/ReactRefreshRequire.dev.test.js',
]

// which types we have configured to run separate
const configuredTestTypes = [UNIT_TEST_EXT]

async function main() {
  let concurrencyIdx = process.argv.indexOf('-c')
  const concurrency =
    parseInt(process.argv[concurrencyIdx + 1], 10) || DEFAULT_CONCURRENCY

  const outputTimings = process.argv.indexOf('--timings') !== -1
  const writeTimings = process.argv.indexOf('--write-timings') !== -1
  const isAzure = process.argv.indexOf('--azure') !== -1
  const groupIdx = process.argv.indexOf('-g')
  const groupArg = groupIdx !== -1 && process.argv[groupIdx + 1]

  const testTypeIdx = process.argv.indexOf('--type')
  const testType = process.argv[testTypeIdx + 1]

  let filterTestsBy

  switch (testType) {
    case 'unit':
      filterTestsBy = UNIT_TEST_EXT
      break
    case 'dev':
      filterTestsBy = DEV_TEST_EXT
      break
    case 'production':
      filterTestsBy = PROD_TEST_EXT
      break
    case 'all':
      filterTestsBy = 'none'
      break
    default:
      break
  }

  console.log('Running tests with concurrency:', concurrency)
  let tests = process.argv.filter((arg) => arg.endsWith('.test.js'))
  let prevTimings

  if (tests.length === 0) {
    tests = (
      await glob('**/*.test.js', {
        nodir: true,
        cwd: path.join(__dirname, 'test'),
      })
    ).filter((test) => {
      // only include the specified type
      if (filterTestsBy) {
        return filterTestsBy === 'none' ? true : test.endsWith(filterTestsBy)
        // include all except the separately configured types
      } else {
        return !configuredTestTypes.some((type) => test.endsWith(type))
      }
    })

    if (outputTimings && groupArg) {
      console.log('Fetching previous timings data')
      try {
        const timingsFile = path.join(__dirname, 'test-timings.json')
        try {
          prevTimings = JSON.parse(await fs.readFile(timingsFile, 'utf8'))
          console.log('Loaded test timings from disk successfully')
        } catch (_) {}

        if (!prevTimings) {
          const timingsRes = await fetch(
            `${TIMINGS_API}?which=${isAzure ? 'azure' : 'actions'}`
          )

          if (!timingsRes.ok) {
            throw new Error(`request status: ${timingsRes.status}`)
          }
          prevTimings = await timingsRes.json()
          console.log('Fetched previous timings data successfully')

          if (writeTimings) {
            await fs.writeFile(timingsFile, JSON.stringify(prevTimings))
            console.log('Wrote previous timings data to', timingsFile)
            process.exit(0)
          }
        }
      } catch (err) {
        console.log(`Failed to fetch timings data`, err)
      }
    }
  }

  let testNames = [
    ...new Set(
      tests.map((f) => {
        let name = `${f.replace(/\\/g, '/').replace(/\/test$/, '')}`
        if (!name.startsWith('test/')) name = `test/${name}`
        return name
      })
    ),
  ]

  if (groupArg) {
    const groupParts = groupArg.split('/')
    const groupPos = parseInt(groupParts[0], 10)
    const groupTotal = parseInt(groupParts[1], 10)
    const numPerGroup = Math.ceil(testNames.length / groupTotal)
    let offset = groupPos === 1 ? 0 : (groupPos - 1) * numPerGroup - 1
    // if there's an odd number of suites give the first group the extra
    if (testNames.length % 2 !== 0 && groupPos !== 1) offset++

    if (prevTimings) {
      const groups = [[]]
      const groupTimes = [0]

      for (const testName of testNames) {
        let smallestGroup = groupTimes[0]
        let smallestGroupIdx = 0

        // get the smallest group time to add current one to
        for (let i = 1; i < groupTotal; i++) {
          if (!groups[i]) {
            groups[i] = []
            groupTimes[i] = 0
          }

          const time = groupTimes[i]
          if (time < smallestGroup) {
            smallestGroup = time
            smallestGroupIdx = i
          }
        }
        groups[smallestGroupIdx].push(testName)
        groupTimes[smallestGroupIdx] += prevTimings[testName] || 1
      }

      const curGroupIdx = groupPos - 1
      testNames = groups[curGroupIdx]

      console.log(
        'Current group previous accumulated times:',
        Math.round(groupTimes[curGroupIdx]) + 's'
      )
    } else {
      testNames = testNames.splice(offset, numPerGroup)
    }
  }
  console.log('Running tests:', '\n', ...testNames.map((name) => `${name}\n`))

  const sema = new Sema(concurrency, { capacity: testNames.length })
  const jestPath = path.join(
    path.dirname(require.resolve('jest-cli/package.json')),
    'bin/jest.js'
  )
  const children = new Set()

  const runTest = (test = '', usePolling) =>
    new Promise((resolve, reject) => {
      const start = new Date().getTime()
      const child = spawn(
        'node',
        [
          jestPath,
          '--runInBand',
          '--forceExit',
          '--verbose',
          ...(isTestJob
            ? ['--json', `--outputFile=${test}${RESULTS_EXT}`]
            : []),
          test,
        ],
        {
          stdio: 'inherit',
          env: {
            JEST_RETRY_TIMES: 0,
            ...process.env,
            ...(isAzure
              ? {
                  HEADLESS: 'true',
                  __POST_PROCESS_MIDDLEWARE_TIME_BUDGET: '50',
                }
              : {}),
            ...(usePolling
              ? {
                  // Events can be finicky in CI. This switches to a more
                  // reliable polling method.
                  CHOKIDAR_USEPOLLING: 'true',
                  CHOKIDAR_INTERVAL: 500,
                }
              : {}),
          },
        }
      )
      children.add(child)
      child.on('exit', (code) => {
        children.delete(child)
        if (code) reject(new Error(`failed with code: ${code}`))
        resolve(new Date().getTime() - start)
      })
    })

  const nonConcurrentTestNames = []

  testNames = testNames.filter((testName) => {
    if (NON_CONCURRENT_TESTS.includes(testName)) {
      nonConcurrentTestNames.push(testName)
      return false
    }
    return true
  })

  // run non-concurrent test names separately and before
  // concurrent ones
  for (const test of nonConcurrentTestNames) {
    let passed = false

    for (let i = 0; i < NUM_RETRIES + 1; i++) {
      try {
        const time = await runTest(test, i > 0)
        timings.push({
          file: test,
          time,
        })
        passed = true
        break
      } catch (err) {
        if (i < NUM_RETRIES) {
          try {
            const testDir = path.dirname(path.join(__dirname, test))
            console.log('Cleaning test files at', testDir)
            await exec(`git clean -fdx "${testDir}"`)
            await exec(`git checkout "${testDir}"`)
          } catch (err) {}
        }
      }
    }
    if (!passed) {
      console.error(`${test} failed to pass within ${NUM_RETRIES} retries`)
      children.forEach((child) => child.kill())

      if (isTestJob) {
        try {
          const testsOutput = await fs.readFile(`${test}${RESULTS_EXT}`, 'utf8')
          console.log(
            `--test output start--`,
            testsOutput,
            `--test output end--`
          )
        } catch (err) {
          console.log(`Failed to load test output`, err)
        }
      }
      process.exit(1)
    }
  }

  await Promise.all(
    testNames.map(async (test) => {
      await sema.acquire()
      let passed = false

      for (let i = 0; i < NUM_RETRIES + 1; i++) {
        try {
          const time = await runTest(test, i > 0)
          timings.push({
            file: test,
            time,
          })
          passed = true
          break
        } catch (err) {
          if (i < NUM_RETRIES) {
            try {
              const testDir = path.dirname(path.join(__dirname, test))
              console.log('Cleaning test files at', testDir)
              await exec(`git clean -fdx "${testDir}"`)
              await exec(`git checkout "${testDir}"`)
            } catch (err) {}
          }
        }
      }
      if (!passed) {
        console.error(`${test} failed to pass within ${NUM_RETRIES} retries`)
        children.forEach((child) => child.kill())

        if (isTestJob) {
          try {
            const testsOutput = await fs.readFile(
              `${test}${RESULTS_EXT}`,
              'utf8'
            )
            console.log(
              `--test output start--`,
              testsOutput,
              `--test output end--`
            )
          } catch (err) {
            console.log(`Failed to load test output`, err)
          }
        }
        process.exit(1)
      }
      sema.release()
    })
  )

  if (outputTimings) {
    const curTimings = {}
    // let junitData = `<testsuites name="jest tests">`
    /*
      <testsuite name="/__tests__/bar.test.js" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-10-10T21:56:49" time="0.323">
        <testcase classname="bar-should be bar" name="bar-should be bar" time="0.004">
        </testcase>
      </testsuite>
    */

    for (const timing of timings) {
      const timeInSeconds = timing.time / 1000
      curTimings[timing.file] = timeInSeconds

      // junitData += `
      //   <testsuite name="${timing.file}" file="${
      //   timing.file
      // }" tests="1" errors="0" failures="0" skipped="0" timestamp="${new Date().toJSON()}" time="${timeInSeconds}">
      //     <testcase classname="tests suite should pass" name="${
      //       timing.file
      //     }" time="${timeInSeconds}"></testcase>
      //   </testsuite>
      // `
    }
    // junitData += `</testsuites>`
    // console.log('output timing data to junit.xml')

    if (prevTimings) {
      try {
        const timingsRes = await fetch(TIMINGS_API, {
          method: 'POST',
          headers: {
            'content-type': 'application/json',
          },
          body: JSON.stringify({
            timings: curTimings,
            which: isAzure ? 'azure' : 'actions',
          }),
        })

        if (!timingsRes.ok) {
          throw new Error(`request status: ${timingsRes.status}`)
        }
        console.log(
          'Sent updated timings successfully',
          await timingsRes.json()
        )
      } catch (err) {
        console.log('Failed to update timings data', err)
      }
    }
  }
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');

const { createController, createService } = require('../core-api');
const getURLFromSegments = require('../utils/url-from-segments');

const pickSchema = obj =>
  _.cloneDeep(
    _.pick(obj, [
      'connection',
      'collectionName',
      'info',
      'options',
      'attributes',
    ])
  );

module.exports = function(strapi) {
  // Retrieve Strapi version.
  strapi.config.uuid = _.get(strapi.config.info, 'strapi.uuid', '');
  strapi.config.info.customs = _.get(strapi.config.info, 'strapi', {});
  strapi.config.info.strapi = (
    _.get(strapi.config, 'info.dependencies.strapi') || ''
  ).replace(/(\^|~)/g, '');
  strapi.config.info.node = process.versions.node;

  // Set connections.
  strapi.connections = {};

  // Set current environment config.
  strapi.config.currentEnvironment =
    strapi.config.environments[strapi.config.environment] || {};

  const defaultConnection =
    strapi.config.currentEnvironment.database.defaultConnection;

  // Set current connections.
  strapi.config.connections = _.get(
    strapi.config.currentEnvironment,
    'database.connections',
    {}
  );

  if (_.get(strapi.config, 'language.enabled')) {
    strapi.config.language.locales = Object.keys(
      _.get(strapi.config, 'locales', {})
    );
  }

  strapi.contentTypes = {};

  Object.keys(strapi.components).forEach(key => {
    const component = strapi.components[key];

    if (!component.connection)
      throw new Error(`Component ${key} is missing a connection attribute`);

    if (!component.collectionName)
      throw new Error(`Component ${key} is missing a collectionName attribute`);

    Object.assign(component, {
      __schema__: pickSchema(component),
      uid: key,
      modelType: 'component',
      globalId:
        component.globalId || _.upperFirst(_.camelCase(`component_${key}`)),
    });
  });

  // Set models.
  strapi.models = Object.keys(strapi.api || []).reduce((acc, apiName) => {
    for (let modelName in strapi.api[apiName].models) {
      let model = strapi.api[apiName].models[modelName];

      Object.assign(model, {
        __schema__: pickSchema(model),
        modelType: 'contentType',
        uid: `application::${apiName}.${modelName}`,
        apiName,
        modelName,
        globalId: model.globalId || _.upperFirst(_.camelCase(modelName)),
        collectionName:
          model.collectionName || `${modelName}`.toLocaleLowerCase(),
        connection: model.connection || defaultConnection,
      });

      strapi.contentTypes[model.uid] = model;

      // find corresponding service and controller
      const userService = _.get(
        strapi.api[apiName],
        ['services', modelName],
        {}
      );
      const userController = _.get(
        strapi.api[apiName],
        ['controllers', modelName],
        {}
      );

      const service = Object.assign(
        createService({ model: modelName, strapi }),
        userService
      );

      const controller = Object.assign(
        createController({ service, model }),
        userController,
        { identity: userController.identity || _.upperFirst(modelName) }
      );

      _.set(strapi.api[apiName], ['services', modelName], service);
      _.set(strapi.api[apiName], ['controllers', modelName], controller);

      acc[modelName] = model;
    }
    return acc;
  }, {});

  // Set controllers.
  strapi.controllers = Object.keys(strapi.api || []).reduce((acc, key) => {
    for (let index in strapi.api[key].controllers) {
      let controller = strapi.api[key].controllers[index];
      controller.identity = controller.identity || _.upperFirst(index);
      acc[index] = controller;
    }

    return acc;
  }, {});

  // Set services.
  strapi.services = Object.keys(strapi.api || []).reduce((acc, key) => {
    for (let index in strapi.api[key].services) {
      acc[index] = strapi.api[key].services[index];
    }

    return acc;
  }, {});

  // Set routes.
  strapi.config.routes = Object.keys(strapi.api || []).reduce((acc, key) => {
    return acc.concat(_.get(strapi.api[key], 'config.routes') || {});
  }, []);

  // Init admin controllers.
  Object.keys(strapi.admin.controllers || []).forEach(key => {
    if (!strapi.admin.controllers[key].identity) {
      strapi.admin.controllers[key].identity = key;
    }
  });

  // Init admin models.
  Object.keys(strapi.admin.models || []).forEach(key => {
    let model = strapi.admin.models[key];

    Object.assign(model, {
      __schema__: pickSchema(model),
      modelType: 'contentType',
      uid: `strapi::${key}`,
      plugin: 'admin',
      modelName: key,
      identity: model.identity || _.upperFirst(key),
      globalId: model.globalId || _.upperFirst(_.camelCase(`admin-${key}`)),
      connection:
        model.connection ||
        strapi.config.currentEnvironment.database.defaultConnection,
    });

    strapi.contentTypes[model.uid] = model;
  });

  Object.keys(strapi.plugins).forEach(pluginName => {
    let plugin = strapi.plugins[pluginName];
    Object.assign(plugin, {
      controllers: plugin.controllers || [],
      services: plugin.services || [],
      models: plugin.models || [],
    });

    Object.keys(plugin.controllers).forEach(key => {
      let controller = plugin.controllers[key];

      Object.assign(controller, {
        identity: controller.identity || key,
      });
    });

    Object.keys(plugin.models || []).forEach(key => {
      let model = plugin.models[key];

      Object.assign(model, {
        __schema__: pickSchema(model),
        modelType: 'contentType',
        modelName: key,
        uid: `plugins::${pluginName}.${key}`,
        plugin: pluginName,
        collectionName:
          model.collectionName || `${pluginName}_${key}`.toLowerCase(),
        globalId:
          model.globalId || _.upperFirst(_.camelCase(`${pluginName}-${key}`)),
        connection:
          model.connection ||
          strapi.config.currentEnvironment.database.defaultConnection,
      });

      strapi.contentTypes[model.uid] = model;
    });
  });

  // Define required middlewares categories.
  const middlewareCategories = ['request', 'response', 'security', 'server'];

  // Flatten middlewares configurations.
  const flattenMiddlewaresConfig = middlewareCategories.reduce((acc, index) => {
    const current = _.merge(strapi.config.currentEnvironment[index], {
      public: _.defaults(strapi.config.public, {
        enabled: true,
      }),
      favicon: _.defaults(strapi.config.favicon, {
        enabled: true,
      }),
    });

    if (_.isObject(current)) {
      acc = _.merge(acc, current);
    } else {
      acc[index] = current;
    }

    return acc;
  }, {});

  // These middlewares cannot be disabled.
  _.merge(flattenMiddlewaresConfig, {
    // Necessary middlewares for the core.
    responses: {
      enabled: true,
    },
    router: {
      enabled: true,
    },
    logger: {
      enabled: true,
    },
    boom: {
      enabled: true,
    },
    cors: {
      enabled: true,
    },
    xframe: {
      enabled: true,
    },
    xss: {
      enabled: true,
    },
  });

  // Exclude database and custom.
  middlewareCategories.push('database');

  // Flatten hooks configurations.
  const flattenHooksConfig = _.pullAll(
    Object.keys(strapi.config.currentEnvironment),
    middlewareCategories
  ).reduce((acc, index) => {
    const current = strapi.config.currentEnvironment[index];

    if (_.isObject(current)) {
      acc = _.merge(acc, {
        [index]: current,
      });
    } else {
      acc[index] = current;
    }

    return acc;
  }, {});

  // Enable hooks and dependencies related to the connections.
  for (let name in strapi.config.connections) {
    const connection = strapi.config.connections[name];
    const connector = connection.connector.replace('strapi-hook-', '');

    enableHookNestedDependencies(strapi, connector, flattenHooksConfig);
  }

  // Preset config in alphabetical order.
  strapi.config.middleware.settings = Object.keys(strapi.middleware).reduce(
    (acc, current) => {
      // Try to find the settings in the current environment, then in the main configurations.
      const currentSettings = _.merge(
        _.get(
          _.cloneDeep(strapi.middleware[current]),
          ['defaults', current],
          {}
        ),
        flattenMiddlewaresConfig[current] ||
          strapi.config.currentEnvironment[current] ||
          strapi.config[current]
      );
      acc[current] = !_.isObject(currentSettings) ? {} : currentSettings;

      if (!_.has(acc[current], 'enabled')) {
        strapi.log.warn(
          `(middleware:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`
        );
      }

      // Ensure that enabled key exist by forcing to false.
      _.defaults(acc[current], { enabled: false });

      return acc;
    },
    {}
  );

  strapi.config.hook.settings = Object.keys(strapi.hook).reduce(
    (acc, current) => {
      // Try to find the settings in the current environment, then in the main configurations.
      const currentSettings = _.merge(
        _.get(_.cloneDeep(strapi.hook[current]), ['defaults', current], {}),
        flattenHooksConfig[current] ||
          _.get(strapi.config.currentEnvironment, ['hook', current]) ||
          _.get(strapi.config, ['hook', current])
      );

      acc[current] = !_.isObject(currentSettings) ? {} : currentSettings;

      if (!_.has(acc[current], 'enabled')) {
        strapi.log.warn(
          `(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`
        );
      }

      // Ensure that enabled key exist by forcing to false.
      _.defaults(acc[current], { enabled: false });

      return acc;
    },
    {}
  );

  // default settings
  strapi.config.port =
    _.get(strapi.config.currentEnvironment, 'server.port') ||
    strapi.config.port;
  strapi.config.host =
    _.get(strapi.config.currentEnvironment, 'server.host') ||
    strapi.config.host;

  // Admin.
  const url = getURLFromSegments({
    hostname: strapi.config.host,
    port: strapi.config.port,
  });

  const adminPath = _.get(
    strapi.config.currentEnvironment.server,
    'admin.path',
    'admin'
  );

  strapi.config.admin.url = new URL(adminPath, url).toString();

  // proxy settings
  const proxy = _.get(strapi.config.currentEnvironment, 'server.proxy', {});
  strapi.config.proxy = proxy;

  // check if proxy is enabled and construct url
  strapi.config.url = proxy.enabled
    ? getURLFromSegments({
        hostname: proxy.host,
        port: proxy.port,
        ssl: proxy.ssl,
      })
    : url;
};

const enableHookNestedDependencies = function(
  strapi,
  name,
  flattenHooksConfig,
  force = false
) {
  // Couldn't find configurations for this hook.
  if (_.isEmpty(_.get(flattenHooksConfig, name, true))) {
    // Check if database connector is used
    const modelsUsed = Object.keys(
      _.assign(_.clone(strapi.api) || {}, strapi.plugins)
    )
      .filter(x =>
        _.isObject(
          _.get(strapi.api, [x, 'models']) ||
            _.get(strapi.plugins, [x, 'models'])
        )
      ) // Filter API with models
      .map(
        x =>
          _.get(strapi.api, [x, 'models']) ||
          _.get(strapi.plugins, [x, 'models'])
      ) // Keep models
      .filter(models => {
        const apiModelsUsed = Object.keys(models).filter(model => {
          const connector = _.get(
            strapi.config.connections,
            models[model].connection,
            {}
          ).connector;

          if (connector) {
            return connector.replace('strapi-hook-', '') === name;
          }

          return false;
        });

        return apiModelsUsed.length !== 0;
      }); // Filter model with the right connector

    flattenHooksConfig[name] = {
      enabled: force || modelsUsed.length > 0, // Will return false if there is no model, else true.
    };

    // Enabled dependencies.
    if (_.get(strapi.hook, `${name}.dependencies`, []).length > 0) {
      strapi.hook[name].dependencies.forEach(dependency => {
        enableHookNestedDependencies(
          strapi,
          dependency.replace('strapi-hook-', ''),
          flattenHooksConfig,
          true
        );
      });
    }
  }
};
<commit_msg>Update admin path variable if proxy is enabled<commit_after>'use strict';

const _ = require('lodash');

const { createController, createService } = require('../core-api');
const getURLFromSegments = require('../utils/url-from-segments');

const pickSchema = obj =>
  _.cloneDeep(
    _.pick(obj, [
      'connection',
      'collectionName',
      'info',
      'options',
      'attributes',
    ])
  );

module.exports = function(strapi) {
  // Retrieve Strapi version.
  strapi.config.uuid = _.get(strapi.config.info, 'strapi.uuid', '');
  strapi.config.info.customs = _.get(strapi.config.info, 'strapi', {});
  strapi.config.info.strapi = (
    _.get(strapi.config, 'info.dependencies.strapi') || ''
  ).replace(/(\^|~)/g, '');
  strapi.config.info.node = process.versions.node;

  // Set connections.
  strapi.connections = {};

  // Set current environment config.
  strapi.config.currentEnvironment =
    strapi.config.environments[strapi.config.environment] || {};

  const defaultConnection =
    strapi.config.currentEnvironment.database.defaultConnection;

  // Set current connections.
  strapi.config.connections = _.get(
    strapi.config.currentEnvironment,
    'database.connections',
    {}
  );

  if (_.get(strapi.config, 'language.enabled')) {
    strapi.config.language.locales = Object.keys(
      _.get(strapi.config, 'locales', {})
    );
  }

  strapi.contentTypes = {};

  Object.keys(strapi.components).forEach(key => {
    const component = strapi.components[key];

    if (!component.connection)
      throw new Error(`Component ${key} is missing a connection attribute`);

    if (!component.collectionName)
      throw new Error(`Component ${key} is missing a collectionName attribute`);

    Object.assign(component, {
      __schema__: pickSchema(component),
      uid: key,
      modelType: 'component',
      globalId:
        component.globalId || _.upperFirst(_.camelCase(`component_${key}`)),
    });
  });

  // Set models.
  strapi.models = Object.keys(strapi.api || []).reduce((acc, apiName) => {
    for (let modelName in strapi.api[apiName].models) {
      let model = strapi.api[apiName].models[modelName];

      Object.assign(model, {
        __schema__: pickSchema(model),
        modelType: 'contentType',
        uid: `application::${apiName}.${modelName}`,
        apiName,
        modelName,
        globalId: model.globalId || _.upperFirst(_.camelCase(modelName)),
        collectionName:
          model.collectionName || `${modelName}`.toLocaleLowerCase(),
        connection: model.connection || defaultConnection,
      });

      strapi.contentTypes[model.uid] = model;

      // find corresponding service and controller
      const userService = _.get(
        strapi.api[apiName],
        ['services', modelName],
        {}
      );
      const userController = _.get(
        strapi.api[apiName],
        ['controllers', modelName],
        {}
      );

      const service = Object.assign(
        createService({ model: modelName, strapi }),
        userService
      );

      const controller = Object.assign(
        createController({ service, model }),
        userController,
        { identity: userController.identity || _.upperFirst(modelName) }
      );

      _.set(strapi.api[apiName], ['services', modelName], service);
      _.set(strapi.api[apiName], ['controllers', modelName], controller);

      acc[modelName] = model;
    }
    return acc;
  }, {});

  // Set controllers.
  strapi.controllers = Object.keys(strapi.api || []).reduce((acc, key) => {
    for (let index in strapi.api[key].controllers) {
      let controller = strapi.api[key].controllers[index];
      controller.identity = controller.identity || _.upperFirst(index);
      acc[index] = controller;
    }

    return acc;
  }, {});

  // Set services.
  strapi.services = Object.keys(strapi.api || []).reduce((acc, key) => {
    for (let index in strapi.api[key].services) {
      acc[index] = strapi.api[key].services[index];
    }

    return acc;
  }, {});

  // Set routes.
  strapi.config.routes = Object.keys(strapi.api || []).reduce((acc, key) => {
    return acc.concat(_.get(strapi.api[key], 'config.routes') || {});
  }, []);

  // Init admin controllers.
  Object.keys(strapi.admin.controllers || []).forEach(key => {
    if (!strapi.admin.controllers[key].identity) {
      strapi.admin.controllers[key].identity = key;
    }
  });

  // Init admin models.
  Object.keys(strapi.admin.models || []).forEach(key => {
    let model = strapi.admin.models[key];

    Object.assign(model, {
      __schema__: pickSchema(model),
      modelType: 'contentType',
      uid: `strapi::${key}`,
      plugin: 'admin',
      modelName: key,
      identity: model.identity || _.upperFirst(key),
      globalId: model.globalId || _.upperFirst(_.camelCase(`admin-${key}`)),
      connection:
        model.connection ||
        strapi.config.currentEnvironment.database.defaultConnection,
    });

    strapi.contentTypes[model.uid] = model;
  });

  Object.keys(strapi.plugins).forEach(pluginName => {
    let plugin = strapi.plugins[pluginName];
    Object.assign(plugin, {
      controllers: plugin.controllers || [],
      services: plugin.services || [],
      models: plugin.models || [],
    });

    Object.keys(plugin.controllers).forEach(key => {
      let controller = plugin.controllers[key];

      Object.assign(controller, {
        identity: controller.identity || key,
      });
    });

    Object.keys(plugin.models || []).forEach(key => {
      let model = plugin.models[key];

      Object.assign(model, {
        __schema__: pickSchema(model),
        modelType: 'contentType',
        modelName: key,
        uid: `plugins::${pluginName}.${key}`,
        plugin: pluginName,
        collectionName:
          model.collectionName || `${pluginName}_${key}`.toLowerCase(),
        globalId:
          model.globalId || _.upperFirst(_.camelCase(`${pluginName}-${key}`)),
        connection:
          model.connection ||
          strapi.config.currentEnvironment.database.defaultConnection,
      });

      strapi.contentTypes[model.uid] = model;
    });
  });

  // Define required middlewares categories.
  const middlewareCategories = ['request', 'response', 'security', 'server'];

  // Flatten middlewares configurations.
  const flattenMiddlewaresConfig = middlewareCategories.reduce((acc, index) => {
    const current = _.merge(strapi.config.currentEnvironment[index], {
      public: _.defaults(strapi.config.public, {
        enabled: true,
      }),
      favicon: _.defaults(strapi.config.favicon, {
        enabled: true,
      }),
    });

    if (_.isObject(current)) {
      acc = _.merge(acc, current);
    } else {
      acc[index] = current;
    }

    return acc;
  }, {});

  // These middlewares cannot be disabled.
  _.merge(flattenMiddlewaresConfig, {
    // Necessary middlewares for the core.
    responses: {
      enabled: true,
    },
    router: {
      enabled: true,
    },
    logger: {
      enabled: true,
    },
    boom: {
      enabled: true,
    },
    cors: {
      enabled: true,
    },
    xframe: {
      enabled: true,
    },
    xss: {
      enabled: true,
    },
  });

  // Exclude database and custom.
  middlewareCategories.push('database');

  // Flatten hooks configurations.
  const flattenHooksConfig = _.pullAll(
    Object.keys(strapi.config.currentEnvironment),
    middlewareCategories
  ).reduce((acc, index) => {
    const current = strapi.config.currentEnvironment[index];

    if (_.isObject(current)) {
      acc = _.merge(acc, {
        [index]: current,
      });
    } else {
      acc[index] = current;
    }

    return acc;
  }, {});

  // Enable hooks and dependencies related to the connections.
  for (let name in strapi.config.connections) {
    const connection = strapi.config.connections[name];
    const connector = connection.connector.replace('strapi-hook-', '');

    enableHookNestedDependencies(strapi, connector, flattenHooksConfig);
  }

  // Preset config in alphabetical order.
  strapi.config.middleware.settings = Object.keys(strapi.middleware).reduce(
    (acc, current) => {
      // Try to find the settings in the current environment, then in the main configurations.
      const currentSettings = _.merge(
        _.get(
          _.cloneDeep(strapi.middleware[current]),
          ['defaults', current],
          {}
        ),
        flattenMiddlewaresConfig[current] ||
          strapi.config.currentEnvironment[current] ||
          strapi.config[current]
      );
      acc[current] = !_.isObject(currentSettings) ? {} : currentSettings;

      if (!_.has(acc[current], 'enabled')) {
        strapi.log.warn(
          `(middleware:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`
        );
      }

      // Ensure that enabled key exist by forcing to false.
      _.defaults(acc[current], { enabled: false });

      return acc;
    },
    {}
  );

  strapi.config.hook.settings = Object.keys(strapi.hook).reduce(
    (acc, current) => {
      // Try to find the settings in the current environment, then in the main configurations.
      const currentSettings = _.merge(
        _.get(_.cloneDeep(strapi.hook[current]), ['defaults', current], {}),
        flattenHooksConfig[current] ||
          _.get(strapi.config.currentEnvironment, ['hook', current]) ||
          _.get(strapi.config, ['hook', current])
      );

      acc[current] = !_.isObject(currentSettings) ? {} : currentSettings;

      if (!_.has(acc[current], 'enabled')) {
        strapi.log.warn(
          `(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`
        );
      }

      // Ensure that enabled key exist by forcing to false.
      _.defaults(acc[current], { enabled: false });

      return acc;
    },
    {}
  );

  // default settings
  strapi.config.port =
    _.get(strapi.config.currentEnvironment, 'server.port') ||
    strapi.config.port;
  strapi.config.host =
    _.get(strapi.config.currentEnvironment, 'server.host') ||
    strapi.config.host;

  // Admin.
  const url = getURLFromSegments({
    hostname: strapi.config.host,
    port: strapi.config.port,
  });

  const adminPath = _.get(
    strapi.config.currentEnvironment.server,
    'admin.path',
    'admin'
  );

  strapi.config.admin.url = new URL(adminPath, url).toString();

  // proxy settings
  const proxy = _.get(strapi.config.currentEnvironment, 'server.proxy', {});
  strapi.config.proxy = proxy;

  // check if proxy is enabled and construct url
  strapi.config.url = proxy.enabled
    ? getURLFromSegments({
        hostname: proxy.host,
        port: proxy.port,
        ssl: proxy.ssl,
      })
    : url;

  // check if proxy enabled and construct admin url
  if (proxy.enabled === true) {
    strapi.config.admin.url = new URL(adminPath, strapi.config.url).toString();
  }
};

const enableHookNestedDependencies = function(
  strapi,
  name,
  flattenHooksConfig,
  force = false
) {
  // Couldn't find configurations for this hook.
  if (_.isEmpty(_.get(flattenHooksConfig, name, true))) {
    // Check if database connector is used
    const modelsUsed = Object.keys(
      _.assign(_.clone(strapi.api) || {}, strapi.plugins)
    )
      .filter(x =>
        _.isObject(
          _.get(strapi.api, [x, 'models']) ||
            _.get(strapi.plugins, [x, 'models'])
        )
      ) // Filter API with models
      .map(
        x =>
          _.get(strapi.api, [x, 'models']) ||
          _.get(strapi.plugins, [x, 'models'])
      ) // Keep models
      .filter(models => {
        const apiModelsUsed = Object.keys(models).filter(model => {
          const connector = _.get(
            strapi.config.connections,
            models[model].connection,
            {}
          ).connector;

          if (connector) {
            return connector.replace('strapi-hook-', '') === name;
          }

          return false;
        });

        return apiModelsUsed.length !== 0;
      }); // Filter model with the right connector

    flattenHooksConfig[name] = {
      enabled: force || modelsUsed.length > 0, // Will return false if there is no model, else true.
    };

    // Enabled dependencies.
    if (_.get(strapi.hook, `${name}.dependencies`, []).length > 0) {
      strapi.hook[name].dependencies.forEach(dependency => {
        enableHookNestedDependencies(
          strapi,
          dependency.replace('strapi-hook-', ''),
          flattenHooksConfig,
          true
        );
      });
    }
  }
};
<|endoftext|>