<commit_before>import GeoJSON from 'geojson';
import { Record } from '../../models/record.model';
import Specie from '../../models/specie.model';
const log = require('../../config/log').logger();

function constructQuery(req) {
  const bmClass = {
    $or: [
      {
        bmClass: {
          $in: req.query.bmClass
            ? req.query.bmClass.constructor === Array
              ? req.query.bmClass
              : [req.query.bmClass]
            : undefined
        }
      }
    ]
  };
  const endemic = { $or: [{ endemic: { $in: [true] } }] };
  const invasive = { $or: [{ invasive: { $in: [true] } }] };
  const endangered = {
    $or: [
      {
        iucn: {
          $in: ['EN', 'VU', 'CR']
        }
      }
    ]
  };
  const query = {};
  // TODO: Remove first level if
  if (
    req.params.species ||
    req.query.bmClass ||
    req.query.endangered ||
    req.query.endemic ||
    req.query.invasive
  ) {
    query.$and = [];
    if (req.query.bmClass) query.$and.push(bmClass);
    if (req.query.endangered) {
      query.$and.push(endangered);
      query.$and.push({ $or: [{ iucn: { $ne: null } }] });
    }
    if (req.query.endemic) query.$and.push(endemic);
    if (req.query.invasive) query.$and.push(invasive);
  }
  return query;
}

/**
 * @swagger
 * /species/records/{taxID}:
 *   get:
 *     description: "Get all records of an species acceptedNameUsage."
 *     operationId: REC1
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           required:
 *             - type
 *             - features
 *           properties:
 *             type:
 *               type: string
 *               default: "FeatureCollection"
 *             features:
 *               type: array
 *               items:
 *                 $ref: "#/definitions/FeatureSpeciesRecord"
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getSpeciesRecords(req, res) {
  if (req.params.taxID) {
    try {
      const docs = await Record.aggregate([
        {
          $match: {
            taxID: +req.params.taxID,
            use: true,
            visualizationPrivileges: 0,
            spatialDuplicated: false
          }
        },
        {
          $project: {
            reported: {
              $cond: {
                if: { $ne: ['$reportedDate', null] },
                then: true,
                else: false
              }
            },
            updated: {
              $cond: {
                if: {
                  $and: [{ $isArray: '$updated' }, { $ne: ['$updated', []] }]
                },
                then: { $isArray: '$updated' },
                else: false
              }
            },
            _id: 1,
            taxID: 1,
            acceptedNameUsage: 1,
            species: 1,
            speciesOriginal: 1,
            verbatimLocality: 1,
            decimalLatitude: 1,
            decimalLongitude: 1,
            verbatimElevation: 1,
            basisOfRecord: 1,
            catalogNumber: 1,
            collectionCode: 1,
            recordedBy: 1,
            institutionCode: 1,
            url: 1,
            day: 1,
            month: 1,
            year: 1,
            suggestedStateProvince: 1,
            suggestedCounty: 1,
            environmentalOutlier: 1,
            source: 1,
            stateProvince: 1,
            county: 1
          }
        }
      ]);
      res.send(
        GeoJSON.parse(docs, { Point: ['decimalLatitude', 'decimalLongitude'] })
      );
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the records');
    }
  }
}

/**
 * @swagger
 * /species/records/group/{taxID}:
 *   get:
 *     description: "Get all records of an species acceptedNameUsage, with privileges for group."
 *     operationId: REC1
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           required:
 *             - type
 *             - features
 *           properties:
 *             type:
 *               type: string
 *               default: "FeatureCollection"
 *             features:
 *               type: array
 *               items:
 *                 $ref: "#/definitions/FeatureSpeciesRecord"
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getSpeciesRecordsWithPrivileges(req, res) {
  if (req.params.taxID) {
    try {
      const docs = await Record.aggregate([
        {
          $match: {
            taxID: +req.params.taxID,
            use: true,
            visualizationPrivileges: { $in: [1, 0] },
            spatialDuplicated: false
          }
        },
        {
          $project: {
            reported: {
              $cond: {
                if: { $ne: ['$reportedDate', null] },
                then: true,
                else: false
              }
            },
            updated: {
              $cond: {
                if: {
                  $and: [{ $isArray: '$updated' }, { $ne: ['$updated', []] }]
                },
                then: { $isArray: '$updated' },
                else: false
              }
            },
            _id: 1,
            taxID: 1,
            acceptedNameUsage: 1,
            species: 1,
            speciesOriginal: 1,
            verbatimLocality: 1,
            decimalLatitude: 1,
            decimalLongitude: 1,
            verbatimElevation: 1,
            basisOfRecord: 1,
            catalogNumber: 1,
            collectionCode: 1,
            recordedBy: 1,
            institutionCode: 1,
            url: 1,
            day: 1,
            month: 1,
            year: 1,
            suggestedStateProvince: 1,
            suggestedCounty: 1,
            environmentalOutlier: 1,
            source: 1,
            stateProvince: 1,
            county: 1
          }
        }
      ]);
      res.send(
        GeoJSON.parse(docs, { Point: ['decimalLatitude', 'decimalLongitude'] })
      );
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the records');
    }
  }
}

/**
 * @swagger
 * /species:
 *   get:
 *     description: "Get all the species in BioModelos"
 *     operationId: SPE1
 *     parameters:
 *       - name: bmClass
 *         in: query
 *         description: // TODO Describe parameter
 *         required: false
 *         type: string
 *       - name: endangered
 *         in: query
 *         description: true to filter endangered species
 *         required: false
 *         type: boolean
 *       - name: endemic
 *         in: query
 *         description: true to filter endemic species
 *         required: false
 *         type: boolean
 *       - name: invasive
 *         in: query
 *         description: true to filter invasive species
 *         required: false
 *         type: boolean
 *       - name: modelStatus
 *         in: query
 *         description: Filter species by model status
 *         required: false
 *         type: string
 *       - name: speciesIn
 *         in: query
 *         description: Filter species by taxID
 *         required: false
 *         schema:
 *           type: array
 *           items:
 *             type: number
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: array
 *           items:
 *             type: object
 *             required:
 *               - taxID
 *             properties:
 *               species:
 *                 type: string
 *               taxID:
 *                 type: number
 *               acceptedNameUsage:
 *                 type: string
 *         examples:
 *           AllSpecies:
 *             taxID: 1
 *             species: Abarema barbouriana
 *           FilterSpecies:
 *             taxID: 1
 *             acceptedNameUsage: Abarema barbouriana
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getAllSpecies(req, res) {
  const query = constructQuery(req);
  try {
    let docs = [];
    if (req.query.modelStatus) {
      docs = await Specie.aggregate([
        {
          $match: query
        },
        {
          $lookup: {
            localField: 'taxID',
            from: 'models',
            foreignField: 'taxID',
            as: 'models'
          }
        },
        {
          $match: { 'models.modelStatus': req.query.modelStatus }
        },
        {
          $project: {
            species: 1,
            taxID: 1,
            _id: 0
          }
        }
      ]).sort({ species: 1 });
    } else if (req.query.speciesIn) {
      docs = await Specie.aggregate([
        {
          $match: {
            taxID: {
              $in: req.query.speciesIn.split(',').map(e => parseInt(e.trim()))
            }
          }
        },
        {
          $group: {
            _id: {
              acceptedNameUsage: '$acceptedNameUsage',
              taxID: '$taxID'
            },
            acceptedNameUsage: { $first: '$acceptedNameUsage' },
            taxID: { $first: '$taxID' }
          }
        },
        { $project: { _id: 0 } },
        { $sort: { acceptedNameUsage: 1 } }
      ]);
    } else {
      docs = await Specie.find(query, {
        species: 1,
        taxID: 1,
        _id: 0
      }).sort({ species: 1 });
    }
    res.json(docs);
  } catch (err) {
    log.error(err);
    res.send('There was an error getting the species');
  }
}

/**
 * @swagger
 * /species/{taxID}:
 *   get:
 *     description: "Get superior taxonomy and the total records of a species"
 *     operationId: SPE2
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           properties:
 *             species:
 *               type: string
 *             family:
 *               type: string
 *             order:
 *               type: string
 *             class:
 *               type: string
 *             phylum:
 *               type: string
 *             kingdom:
 *               type: string
 *             acceptedNameUsage:
 *               type: string
 *             totalRecords:
 *               type: number
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getTaxonomyAndTotalRecords(req, res) {
  if (req.params.taxID) {
    try {
      const doc = await Specie.find(
        { taxID: +req.params.taxID },
        {
          _id: 0,
          acceptedNameUsage: 1,
          species: 1,
          kingdom: 1,
          phylum: 1,
          class: 1,
          order: 1,
          family: 1
        }
      );
      try {
        const doc2 = await Record.aggregate([
          {
            $match: {
              taxID: +req.params.taxID,
              use: true,
              spatialDuplicated: false
            }
          },
          {
            $group: {
              _id: {
                characteristics: { taxID: '$taxID' }
              },
              totalRecords: { $sum: 1 }
            }
          },
          {
            $project: {
              _id: 0,
              taxID: '$_id.characteristics.taxID',
              totalRecords: '$totalRecords'
            }
          }
        ]);
        if (doc2[0] && doc2[0].totalRecords) {
          doc[0]['_doc']['totalRecords'] = doc2[0].totalRecords;
        } else {
          doc[0]['_doc']['totalRecords'] = 0;
        }
        res.json(doc);
      } catch (err) {
        log.error(err);
        res.send('There was an error getting the total records');
      }
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species information');
    }
  }
}

/**
 * @swagger
 * /species/search/{species}:
 *   get:
 *     description: Search species by its attribute "species". If no species is passed, it behaves like /species endpoint
 *     operationId: SPE3
 *     parameters:
 *       - name: species
 *         in: path
 *         description: The scientific name to filter by.
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           properties:
 *             species:
 *               type: string
 *             endemic:
 *               type: boolean
 *             invasive:
 *               type: boolean
 *             iucn:
 *               type: string
 *             taxonomicStatus:
 *               type: string
 *             bmClass:
 *               type: string
 *             taxID:
 *               type: number
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function searchSpecies(req, res) {
  const query = constructQuery(req);
  if (req.params.species) {
    // eslint-disable-next-line security/detect-non-literal-regexp
    const regEx = new RegExp(
      req.params.species.replace(/[#-.]|[[-^]|[?|{}]/g, '\\$&'),
      'ig'
    );
    query.$and.push({ species: { $regex: regEx } });
  }
  if (req.query.modelStatus) {
    try {
      const docs = await Specie.aggregate([
        {
          $match: query
        },
        {
          $lookup: {
            localField: 'taxID',
            from: 'models',
            foreignField: 'taxID',
            as: 'models'
          }
        },
        {
          $match: { 'models.modelStatus': req.query.modelStatus }
        },
        {
          $project: {
            _id: 0,
            species: 1,
            taxID: 1,
            taxonomicStatus: 1,
            iucn: 1,
            bmClass: 1,
            endemic: 1,
            invasive: 1
          }
        }
      ]).sort({ species: 1 });
      res.json(docs);
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species');
    }
  } else {
    try {
      const docs = await Specie.find(query, {
        _id: 0,
        species: 1,
        taxID: 1,
        taxonomicStatus: 1,
        iucn: 1,
        bmClass: 1,
        endemic: 1,
        invasive: 1
      }).sort({ species: 1 });
      res.json(docs);
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species');
    }
  }
}
<commit_msg>Fix condition to determine if a record is reported<commit_after>import GeoJSON from 'geojson';
import { Record } from '../../models/record.model';
import Specie from '../../models/specie.model';
const log = require('../../config/log').logger();

function constructQuery(req) {
  const bmClass = {
    $or: [
      {
        bmClass: {
          $in: req.query.bmClass
            ? req.query.bmClass.constructor === Array
              ? req.query.bmClass
              : [req.query.bmClass]
            : undefined
        }
      }
    ]
  };
  const endemic = { $or: [{ endemic: { $in: [true] } }] };
  const invasive = { $or: [{ invasive: { $in: [true] } }] };
  const endangered = {
    $or: [
      {
        iucn: {
          $in: ['EN', 'VU', 'CR']
        }
      }
    ]
  };
  const query = {};
  // TODO: Remove first level if
  if (
    req.params.species ||
    req.query.bmClass ||
    req.query.endangered ||
    req.query.endemic ||
    req.query.invasive
  ) {
    query.$and = [];
    if (req.query.bmClass) query.$and.push(bmClass);
    if (req.query.endangered) {
      query.$and.push(endangered);
      query.$and.push({ $or: [{ iucn: { $ne: null } }] });
    }
    if (req.query.endemic) query.$and.push(endemic);
    if (req.query.invasive) query.$and.push(invasive);
  }
  return query;
}

/**
 * @swagger
 * /species/records/{taxID}:
 *   get:
 *     description: "Get all records of an species acceptedNameUsage."
 *     operationId: REC1
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           required:
 *             - type
 *             - features
 *           properties:
 *             type:
 *               type: string
 *               default: "FeatureCollection"
 *             features:
 *               type: array
 *               items:
 *                 $ref: "#/definitions/FeatureSpeciesRecord"
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getSpeciesRecords(req, res) {
  if (req.params.taxID) {
    try {
      const docs = await Record.aggregate([
        {
          $match: {
            taxID: +req.params.taxID,
            use: true,
            visualizationPrivileges: 0,
            spatialDuplicated: false
          }
        },
        {
          $project: {
            reported: {
              $cond: {
                if: { $ne: ['$reportedDate', null] },
                then: true,
                else: false
              }
            },
            updated: {
              $cond: {
                if: {
                  $and: [{ $isArray: '$updated' }, { $ne: ['$updated', []] }]
                },
                then: { $isArray: '$updated' },
                else: false
              }
            },
            _id: 1,
            taxID: 1,
            acceptedNameUsage: 1,
            species: 1,
            speciesOriginal: 1,
            verbatimLocality: 1,
            decimalLatitude: 1,
            decimalLongitude: 1,
            verbatimElevation: 1,
            basisOfRecord: 1,
            catalogNumber: 1,
            collectionCode: 1,
            recordedBy: 1,
            institutionCode: 1,
            url: 1,
            day: 1,
            month: 1,
            year: 1,
            suggestedStateProvince: 1,
            suggestedCounty: 1,
            environmentalOutlier: 1,
            source: 1,
            stateProvince: 1,
            county: 1
          }
        }
      ]);
      res.send(
        GeoJSON.parse(docs, { Point: ['decimalLatitude', 'decimalLongitude'] })
      );
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the records');
    }
  }
}

/**
 * @swagger
 * /species/records/group/{taxID}:
 *   get:
 *     description: "Get all records of an species acceptedNameUsage, with privileges for group."
 *     operationId: REC1
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           required:
 *             - type
 *             - features
 *           properties:
 *             type:
 *               type: string
 *               default: "FeatureCollection"
 *             features:
 *               type: array
 *               items:
 *                 $ref: "#/definitions/FeatureSpeciesRecord"
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getSpeciesRecordsWithPrivileges(req, res) {
  if (req.params.taxID) {
    try {
      const docs = await Record.aggregate([
        {
          $match: {
            taxID: +req.params.taxID,
            use: true,
            visualizationPrivileges: { $in: [1, 0] },
            spatialDuplicated: false
          }
        },
        {
          $project: {
            reported: {
              $cond: {
                if: { $ifNull: ['$reportedDate', false] },
                then: true,
                else: false
              }
            },
            updated: {
              $cond: {
                if: {
                  $and: [{ $isArray: '$updated' }, { $ne: ['$updated', []] }]
                },
                then: { $isArray: '$updated' },
                else: false
              }
            },
            _id: 1,
            taxID: 1,
            acceptedNameUsage: 1,
            species: 1,
            speciesOriginal: 1,
            verbatimLocality: 1,
            decimalLatitude: 1,
            decimalLongitude: 1,
            verbatimElevation: 1,
            basisOfRecord: 1,
            catalogNumber: 1,
            collectionCode: 1,
            recordedBy: 1,
            institutionCode: 1,
            url: 1,
            day: 1,
            month: 1,
            year: 1,
            suggestedStateProvince: 1,
            suggestedCounty: 1,
            environmentalOutlier: 1,
            source: 1,
            stateProvince: 1,
            county: 1
          }
        }
      ]);
      res.send(
        GeoJSON.parse(docs, { Point: ['decimalLatitude', 'decimalLongitude'] })
      );
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the records');
    }
  }
}

/**
 * @swagger
 * /species:
 *   get:
 *     description: "Get all the species in BioModelos"
 *     operationId: SPE1
 *     parameters:
 *       - name: bmClass
 *         in: query
 *         description: // TODO Describe parameter
 *         required: false
 *         type: string
 *       - name: endangered
 *         in: query
 *         description: true to filter endangered species
 *         required: false
 *         type: boolean
 *       - name: endemic
 *         in: query
 *         description: true to filter endemic species
 *         required: false
 *         type: boolean
 *       - name: invasive
 *         in: query
 *         description: true to filter invasive species
 *         required: false
 *         type: boolean
 *       - name: modelStatus
 *         in: query
 *         description: Filter species by model status
 *         required: false
 *         type: string
 *       - name: speciesIn
 *         in: query
 *         description: Filter species by taxID
 *         required: false
 *         schema:
 *           type: array
 *           items:
 *             type: number
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: array
 *           items:
 *             type: object
 *             required:
 *               - taxID
 *             properties:
 *               species:
 *                 type: string
 *               taxID:
 *                 type: number
 *               acceptedNameUsage:
 *                 type: string
 *         examples:
 *           AllSpecies:
 *             taxID: 1
 *             species: Abarema barbouriana
 *           FilterSpecies:
 *             taxID: 1
 *             acceptedNameUsage: Abarema barbouriana
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getAllSpecies(req, res) {
  const query = constructQuery(req);
  try {
    let docs = [];
    if (req.query.modelStatus) {
      docs = await Specie.aggregate([
        {
          $match: query
        },
        {
          $lookup: {
            localField: 'taxID',
            from: 'models',
            foreignField: 'taxID',
            as: 'models'
          }
        },
        {
          $match: { 'models.modelStatus': req.query.modelStatus }
        },
        {
          $project: {
            species: 1,
            taxID: 1,
            _id: 0
          }
        }
      ]).sort({ species: 1 });
    } else if (req.query.speciesIn) {
      docs = await Specie.aggregate([
        {
          $match: {
            taxID: {
              $in: req.query.speciesIn.split(',').map(e => parseInt(e.trim()))
            }
          }
        },
        {
          $group: {
            _id: {
              acceptedNameUsage: '$acceptedNameUsage',
              taxID: '$taxID'
            },
            acceptedNameUsage: { $first: '$acceptedNameUsage' },
            taxID: { $first: '$taxID' }
          }
        },
        { $project: { _id: 0 } },
        { $sort: { acceptedNameUsage: 1 } }
      ]);
    } else {
      docs = await Specie.find(query, {
        species: 1,
        taxID: 1,
        _id: 0
      }).sort({ species: 1 });
    }
    res.json(docs);
  } catch (err) {
    log.error(err);
    res.send('There was an error getting the species');
  }
}

/**
 * @swagger
 * /species/{taxID}:
 *   get:
 *     description: "Get superior taxonomy and the total records of a species"
 *     operationId: SPE2
 *     parameters:
 *       - name: taxID
 *         in: path
 *         description: The taxon ID of the specie
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           properties:
 *             species:
 *               type: string
 *             family:
 *               type: string
 *             order:
 *               type: string
 *             class:
 *               type: string
 *             phylum:
 *               type: string
 *             kingdom:
 *               type: string
 *             acceptedNameUsage:
 *               type: string
 *             totalRecords:
 *               type: number
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function getTaxonomyAndTotalRecords(req, res) {
  if (req.params.taxID) {
    try {
      const doc = await Specie.find(
        { taxID: +req.params.taxID },
        {
          _id: 0,
          acceptedNameUsage: 1,
          species: 1,
          kingdom: 1,
          phylum: 1,
          class: 1,
          order: 1,
          family: 1
        }
      );
      try {
        const doc2 = await Record.aggregate([
          {
            $match: {
              taxID: +req.params.taxID,
              use: true,
              spatialDuplicated: false
            }
          },
          {
            $group: {
              _id: {
                characteristics: { taxID: '$taxID' }
              },
              totalRecords: { $sum: 1 }
            }
          },
          {
            $project: {
              _id: 0,
              taxID: '$_id.characteristics.taxID',
              totalRecords: '$totalRecords'
            }
          }
        ]);
        if (doc2[0] && doc2[0].totalRecords) {
          doc[0]['_doc']['totalRecords'] = doc2[0].totalRecords;
        } else {
          doc[0]['_doc']['totalRecords'] = 0;
        }
        res.json(doc);
      } catch (err) {
        log.error(err);
        res.send('There was an error getting the total records');
      }
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species information');
    }
  }
}

/**
 * @swagger
 * /species/search/{species}:
 *   get:
 *     description: Search species by its attribute "species". If no species is passed, it behaves like /species endpoint
 *     operationId: SPE3
 *     parameters:
 *       - name: species
 *         in: path
 *         description: The scientific name to filter by.
 *         required: true
 *         type: string
 *     responses:
 *       "200":
 *         description: Success
 *         schema:
 *           type: object
 *           properties:
 *             species:
 *               type: string
 *             endemic:
 *               type: boolean
 *             invasive:
 *               type: boolean
 *             iucn:
 *               type: string
 *             taxonomicStatus:
 *               type: string
 *             bmClass:
 *               type: string
 *             taxID:
 *               type: number
 *     default:
 *       description: Error
 *       schema:
 *         $ref: "#/definitions/ErrorResponse"
 */
export async function searchSpecies(req, res) {
  const query = constructQuery(req);
  if (req.params.species) {
    // eslint-disable-next-line security/detect-non-literal-regexp
    const regEx = new RegExp(
      req.params.species.replace(/[#-.]|[[-^]|[?|{}]/g, '\\$&'),
      'ig'
    );
    query.$and.push({ species: { $regex: regEx } });
  }
  if (req.query.modelStatus) {
    try {
      const docs = await Specie.aggregate([
        {
          $match: query
        },
        {
          $lookup: {
            localField: 'taxID',
            from: 'models',
            foreignField: 'taxID',
            as: 'models'
          }
        },
        {
          $match: { 'models.modelStatus': req.query.modelStatus }
        },
        {
          $project: {
            _id: 0,
            species: 1,
            taxID: 1,
            taxonomicStatus: 1,
            iucn: 1,
            bmClass: 1,
            endemic: 1,
            invasive: 1
          }
        }
      ]).sort({ species: 1 });
      res.json(docs);
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species');
    }
  } else {
    try {
      const docs = await Specie.find(query, {
        _id: 0,
        species: 1,
        taxID: 1,
        taxonomicStatus: 1,
        iucn: 1,
        bmClass: 1,
        endemic: 1,
        invasive: 1
      }).sort({ species: 1 });
      res.json(docs);
    } catch (err) {
      log.error(err);
      res.send('There was an error getting the species');
    }
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2014-2015 University of Stuttgart
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var express = require('express');
var router = express.Router();
var date_format = require('dateformat');

function is_defined(variable) {
    return (typeof variable !== 'undefined');
}

router.get('/', function(req, res, next) {
    var client = req.app.get('elastic'),
      query = '{ "query": { "match_all": {} } }',
      json = {},
      size = 1000;

    client.search({
        index: 'mf',
        type: 'experiments',
        searchType: 'count'
    }, function(error, response) {
        if (response.hits != undefined) {
            size = response.hits.total;
        }

        client.search({
            index: 'mf',
            type: 'experiments',
            body: query,
            size: size,
            sort: '@timestamp:desc'
        }, function(error, response) {
            if (response.hits != undefined) {
                var results = response.hits.hits;
                json = get_details(results);
            } else {
                json.error = 'No data found in the database.';
            }
            res.json(json);
        });
    });
});

function is_defined(variable) {
    return (typeof variable !== 'undefined');
}

function isEmpty(obj) {
    return Object.keys(obj).length === 0;
}

function get_details(results) {
    var keys = Object.keys(results),
      item = {},
      response = [];
    keys.forEach(function(key) {
        item = results[key]._source;
        if (isEmpty(item)) {
            return;
        }

        /* update time format */
        item.id = results[key]._id;
        item.timestamp = date_format(item['@timestamp'], "yyyy/mm/dd' 'HH:MM");
        delete item['@timestamp'];

        /* set missing job id */
        if (!is_defined(item.job_id)) {
            item.job_id = 'n/a';
        }
        response.push(item);
    });

    return response;
}

/*
 * variable used by /visualization
 */
var skip_fields = ['Timestamp', 'type', 'hostname'];

/** @brief Returns the actual sampled data for a given experiment ID
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return a JSON array with metric data
 */
router.get('/executions/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    var from_time = req.params.from;
    var to_time = req.params.to;
    var result_size = 1000;
    if (req.query.size) {
        result_size = req.query.size;
    }

    client.search({
        index: req.params.ID.toLowerCase(),
        size: result_size,
        sort: ["type", "Timestamp"],
    }, function(err, result) {
        if (err) {
            console.log('Error searching for the values of a specific benchmark: ' + err);
            res.send(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var es_result = [];
                var keys = Object.keys(only_results);
                var power_result = {};

                keys.forEach(function(key) {
                    var data = only_results[key]._source;
                    /*
                     * if metric is from external power measurement system, then
                     * we have to do some more pre-processing. otherwise, we
                     * just add the metric data to our response
                     */
                    if (data.type != "power") {
                        es_result.push(data);
                        return;
                    }

                    var processed = false;
                    for (var key in data) {
                        if (processed)
                            return;
                        if (data.hasOwnProperty(key)) {
                            if (skip_fields.indexOf(key) > -1 || key == '')
                                continue;
                            /*
                             * parse and simplify time-stamp
                             * step is required to be compatible with Rickshaw
                             */
                            var value = parseInt(data[key]);
                            var time = data['Timestamp']; // 1430646029.762737460
                            time = time.toString();
                            time = time.substring(0, 13); // 1430646029.76
                            var metrics = power_result[time];
                            if (!metrics) {
                                metrics = {};
                                metrics.Timestamp = time;
                                metrics.type = data.type;
                            }
                            metrics[key] = value;
                            power_result[time] = metrics;
                            processed = true;
                        }
                    }
                });
                /*
                 * now, add the updated power metrics
                 */
                for (var key in power_result) {
                    es_result.push(power_result[key]);
                }
                res.json(es_result);
            } else {
                res.send('No data in the DB');
            }
        }
    })
});

/** @brief Returns the runtime for a given experiment ID
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return runtime information of a given experiment
 */
router.get('/executions/:ID/time', function(req, res, next) {
    var client = req.app.get('elastic');

    /*
     * get the earliest time-stamp by sorting in ascending order
     */
    client.search({
        index: req.params.ID.toLowerCase(),
        size: 1,
        sort: ["Timestamp:asc"],
    }, function(err, result) {
        var start;
        var end;

        if (err) {
            console.log('Error searching for the values of a specific benchmark: ' + err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var keys = Object.keys(only_results);
                keys.forEach(function(key) {
                    var metric_data = only_results[key]._source;
                    start = metric_data.Timestamp;
                });
            }
        }

        /*
         * get the latest time-stamp by sorting in descending order
         */
        client.search({
            index: req.params.ID.toLowerCase(),
            size: 1,
            sort: ["Timestamp:desc"],
        }, function(err, result) {
            var response;
            if (err) {
                console.log('Error searching for the values of a specific benchmark: ' + err);
            } else {
                if (result.hits != undefined) {
                    var only_results = result.hits.hits;
                    var keys = Object.keys(only_results);
                    keys.forEach(function(key) {
                        var metric_data = only_results[key]._source;
                        end = metric_data.Timestamp;
                    });
                }
            }

            /*
             * create response object
             */
            var response = '{ "start": ' + start + ', "end": ' + end + ', "duration": ' + (end - start) + ' }';
            res.send(response);
        });
    });
});

/** @brief Return details for a given experiment
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return details about an experiment as shown on the front-page
 */
router.get('/executions/details/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    client.get({
        index: 'executions',
        type: 'TBD',
        id: req.params.ID
    }, function(err, result) {
        if (result.found != false) {
            res.send(result._source);
        } else {
            res.send('Requested resource was Not found');
        }
    });
});

/** @brief Return all available metrics sampled for a given experiment
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return list of sampled metrics
 */
router.get('/executions/metrics/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    var id = req.params.ID.toLowerCase();
    client.indices.getMapping({
            index: req.params.ID.toLowerCase(),
        },
        function(err, result) {
            if (err) {
                console.log('Error searching metrics of a specific execution: ' + err);
                res.send(err);
            } else {
                var metrics = result[id].mappings.TBD.properties;
                var names = [];
                var metric_name = Object.keys(metrics);
                metric_name.forEach(function(metric) {
                    if (metric != "Timestamp" && metric != "type") {
                        names.push(metric);
                    }
                });
                res.send(names);
            }
        })
});

/** @brief Returns basic statistics for a given experiment, metric, and time interval
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return JSON document having statistics such as standard deviation
 */
router.get('/execution/stats/:ID/:metric/:from/:to', function(req, res, next) {
    var client = req.app.get('elastic');

    var metric_name = req.params.metric;
    var from_time = req.params.from;
    var to_time = req.params.to;

    client.search({
        index: req.params.ID.toLowerCase(),
        size: 0,
        body: {
            aggs: {
                range_metric: {
                    filter: {
                        range: {
                            "Timestamp": {
                                "from": from_time,
                                "to": to_time
                            }
                        }
                    },
                    aggs: {
                        "extended_stats_metric": {
                            extended_stats: {
                                "field": metric_name
                            }
                        }
                    }
                }
            }
        }
    }, function(err, result) {
        if (err) {
            res.status(500);
            return next(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.aggregations.range_metric.extended_stats_metric;
                res.send(only_results);
            } else {
                res.send('No data in the DB');
            }
        }
    })
});

/** @brief Filters sampled data based on given time interval
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return same as /executions/:ID
 */
router.get('/executions/:ID/:from/:to', function(req, res, next) {
    var client = req.app.get('elastic');

    var from_time = req.params.from;
    var to_time = req.params.to;

    client.search({
        index: req.params.ID.toLowerCase(),
        body: {
            query: {
                constant_score: {
                    filter: {
                        range: {
                            "Timestamp": {
                                "from": from_time,
                                "to": to_time
                            }
                        }
                    }
                }
            }
        }
    }, function(err, result) {
        if (err) {
            res.status(500);
            return next(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var es_result = [];
                var keys = Object.keys(only_results);
                keys.forEach(function(key) {
                    es_result.push(only_results[key]._source);
                });
                res.send(es_result);
            } else {
                res.send('No data in the DB');
            }
        }
    });
});

module.exports = router;
<commit_msg>change of error message<commit_after>/*
 * Copyright (C) 2014-2015 University of Stuttgart
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var express = require('express');
var router = express.Router();
var date_format = require('dateformat');

function is_defined(variable) {
    return (typeof variable !== 'undefined');
}

router.get('/', function(req, res, next) {
    var client = req.app.get('elastic'),
      query = '{ "query": { "match_all": {} } }',
      json = {},
      size = 1000;

    client.search({
        index: 'mf',
        type: 'experiments',
        searchType: 'count'
    }, function(error, response) {
        if (response.hits != undefined) {
            size = response.hits.total;
        }

        client.search({
            index: 'mf',
            type: 'experiments',
            body: query,
            size: size,
            sort: '@timestamp:desc'
        }, function(error, response) {
            if (response.hits != undefined) {
                var results = response.hits.hits;
                json = get_details(results);
            } else {
                json.error = 'No data found in the database.';
            }
            res.json(json);
        });
    });
});

function is_defined(variable) {
    return (typeof variable !== 'undefined');
}

function isEmpty(obj) {
    return Object.keys(obj).length === 0;
}

function get_details(results) {
    var keys = Object.keys(results),
      item = {},
      response = [];
    keys.forEach(function(key) {
        item = results[key]._source;
        if (isEmpty(item)) {
            return;
        }

        /* update time format */
        item.id = results[key]._id;
        item.timestamp = date_format(item['@timestamp'], "yyyy/mm/dd' 'HH:MM");
        delete item['@timestamp'];

        /* set missing job id */
        if (!is_defined(item.job_id)) {
            item.job_id = 'n/a';
        }
        response.push(item);
    });

    return response;
}

/*
 * variable used by /visualization
 */
var skip_fields = ['Timestamp', 'type', 'hostname'];

/** @brief Returns the actual sampled data for a given experiment ID
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return a JSON array with metric data
 */
router.get('/executions/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    var from_time = req.params.from;
    var to_time = req.params.to;
    var result_size = 1000;
    if (req.query.size) {
        result_size = req.query.size;
    }

    client.search({
        index: req.params.ID.toLowerCase(),
        size: result_size,
        sort: ["type", "Timestamp"],
    }, function(err, result) {
        if (err) {
            console.log('Error searching for the values of a specific benchmark: ' + err);
            res.send(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var es_result = [];
                var keys = Object.keys(only_results);
                var power_result = {};

                keys.forEach(function(key) {
                    var data = only_results[key]._source;
                    /*
                     * if metric is from external power measurement system, then
                     * we have to do some more pre-processing. otherwise, we
                     * just add the metric data to our response
                     */
                    if (data.type != "power") {
                        es_result.push(data);
                        return;
                    }

                    var processed = false;
                    for (var key in data) {
                        if (processed)
                            return;
                        if (data.hasOwnProperty(key)) {
                            if (skip_fields.indexOf(key) > -1 || key == '')
                                continue;
                            /*
                             * parse and simplify time-stamp
                             * step is required to be compatible with Rickshaw
                             */
                            var value = parseInt(data[key]);
                            var time = data['Timestamp']; // 1430646029.762737460
                            time = time.toString();
                            time = time.substring(0, 13); // 1430646029.76
                            var metrics = power_result[time];
                            if (!metrics) {
                                metrics = {};
                                metrics.Timestamp = time;
                                metrics.type = data.type;
                            }
                            metrics[key] = value;
                            power_result[time] = metrics;
                            processed = true;
                        }
                    }
                });
                /*
                 * now, add the updated power metrics
                 */
                for (var key in power_result) {
                    es_result.push(power_result[key]);
                }
                res.json(es_result);
            } else {
                res.send('No data in the DB');
            }
        }
    })
});

/** @brief Returns the runtime for a given experiment ID
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return runtime information of a given experiment
 */
router.get('/executions/:ID/time', function(req, res, next) {
    var client = req.app.get('elastic');

    /*
     * get the earliest time-stamp by sorting in ascending order
     */
    client.search({
        index: req.params.ID.toLowerCase(),
        size: 1,
        sort: ["Timestamp:asc"],
    }, function(err, result) {
        var start;
        var end;

        if (err) {
            console.log('Error searching for the values of a specific benchmark: ' + err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var keys = Object.keys(only_results);
                keys.forEach(function(key) {
                    var metric_data = only_results[key]._source;
                    start = metric_data.Timestamp;
                });
            }
        }

        /*
         * get the latest time-stamp by sorting in descending order
         */
        client.search({
            index: req.params.ID.toLowerCase(),
            size: 1,
            sort: ["Timestamp:desc"],
        }, function(err, result) {
            var response;
            if (err) {
                console.log('Error searching for the values of a specific benchmark: ' + err);
            } else {
                if (result.hits != undefined) {
                    var only_results = result.hits.hits;
                    var keys = Object.keys(only_results);
                    keys.forEach(function(key) {
                        var metric_data = only_results[key]._source;
                        end = metric_data.Timestamp;
                    });
                }
            }

            /*
             * create response object
             */
            var response = '{ "start": ' + start + ', "end": ' + end + ', "duration": ' + (end - start) + ' }';
            res.send(response);
        });
    });
});

/** @brief Return details for a given experiment
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return details about an experiment as shown on the front-page
 */
router.get('/executions/details/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    client.get({
        index: 'executions',
        type: 'TBD',
        id: req.params.ID
    }, function(err, result) {
        if (result.found != false) {
            res.send(result._source);
        } else {
            res.send('Requested resource was Not found');
        }
    });
});

/** @brief Return all available metrics sampled for a given experiment
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return list of sampled metrics
 */
router.get('/executions/metrics/:ID', function(req, res, next) {
    var client = req.app.get('elastic');

    var id = req.params.ID.toLowerCase();
    client.indices.getMapping({
            index: req.params.ID.toLowerCase(),
        },
        function(err, result) {
            if (err) {
                console.log('Error searching metrics of a specific execution: ' + err);
                res.send(err);
            } else {
                var metrics = result[id].mappings.TBD.properties;
                var names = [];
                var metric_name = Object.keys(metrics);
                metric_name.forEach(function(metric) {
                    if (metric != "Timestamp" && metric != "type") {
                        names.push(metric);
                    }
                });
                res.send(names);
            }
        })
});

/** @brief Returns basic statistics for a given experiment, metric, and time interval
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return JSON document having statistics such as standard deviation
 */
router.get('/execution/stats/:ID/:metric/:from/:to', function(req, res, next) {
    var client = req.app.get('elastic');

    var metric_name = req.params.metric;
    var from_time = req.params.from;
    var to_time = req.params.to;

    client.search({
        index: req.params.ID.toLowerCase(),
        size: 0,
        body: {
            aggs: {
                range_metric: {
                    filter: {
                        range: {
                            "Timestamp": {
                                "from": from_time,
                                "to": to_time
                            }
                        }
                    },
                    aggs: {
                        "extended_stats_metric": {
                            extended_stats: {
                                "field": metric_name
                            }
                        }
                    }
                }
            }
        }
    }, function(err, result) {
        if (err) {
            console.log('Error doing statistics of a metric of a specific execution: ' + err);
            res.send(err);
            //res.status(500);
            //return next(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.aggregations.range_metric.extended_stats_metric;
                res.send(only_results);
            } else {
                res.send('Getting statistics of a metric of an execution failed.');
            }
        }
    })
});

/** @brief Filters sampled data based on given time interval
 *
 * @param req the request object
 * @param res the response object
 * @param next error handler
 *
 * @return same as /executions/:ID
 */
router.get('/executions/:ID/:from/:to', function(req, res, next) {
    var client = req.app.get('elastic');

    var from_time = req.params.from;
    var to_time = req.params.to;

    client.search({
        index: req.params.ID.toLowerCase(),
        body: {
            query: {
                constant_score: {
                    filter: {
                        range: {
                            "Timestamp": {
                                "from": from_time,
                                "to": to_time
                            }
                        }
                    }
                }
            }
        }
    }, function(err, result) {
        if (err) {
            console.log('Error filtering sampled data based on given time interval: ' + err);
            res.send(err);
            //res.status(500);
            //return next(err);
        } else {
            if (result.hits != undefined) {
                var only_results = result.hits.hits;
                var es_result = [];
                var keys = Object.keys(only_results);
                keys.forEach(function(key) {
                    es_result.push(only_results[key]._source);
                });
                res.send(es_result);
            } else {
                res.send('Getting data based on given time interval failed.');
            }
        }
    });
});

module.exports = router;
<|endoftext|>


<commit_before>var Promise = require('bluebird');
var AdmZip  = require('adm-zip');
var base    = process.env.PWD;
var rekt    = require('rekt').rekt;
var Book    = requireLocal('models/book.js');
var Page    = requireLocal('models/page.js');
var _       = require('lodash');
var fs      = require('fs');

module.exports = function(router) {
  router.route('/export.zip')
    .all(function(req, res, next) {
      if (req.token.subject === 'Tiltfactor' || req.token.subject === 'BHL') {
        next();
      } else {
        res.status(401);
        res.json({
          message: 'You don\'t have access to this.'
        });
      }
    })
    .get(function(req, res) {
      getBooks()
        .then(function(books) {
          res.send('hello');
        });
    });
};

function getBooks() {
  return new Promise(function(resolve, reject) {
    Book.find({})
      .lean()
      .populate({
        path: 'pages'
      })
      .exec(function(err, pages) {
        var options = {
          path: 'pages.differences',
          model: 'Difference'
        };

        if (err) {
          reject(err)
        } else {
          Book.populate(pages, options, function(err, books) {
            if (err) {
              reject(err)
            }
            resolve(books);
          });
        }
      });
  }).then(function(books) {
    if (books.length == 0) {
      return 0
    }
  });
}

// function getBooks() {
//   return new Promise(function(resolve, reject) {
//     Book.find({})
//       .lean()
//       .populate({
//         path: 'pages'
//       })
//       .exec(function(err, pages) {
//         var options = {
//           path: 'pages.differences',
//           model: 'Difference'
//         };

//         if (err) {
//           reject(err);
//         } else {
//           Book.populate(pages, options, function(err, books) {
//             if (err) reject(err);
//             resolve(books);
//           });
//         }
//       });
//   });
// }

// function prepareBooks(books) {
//   return Promise.map(books, function(book) {
//     return {
//       items: [{
//         id: book.id,
//         barcode: book.barcode,
//         pages: []
//       }],
//       oldBook: book
//     };
//   })
//   .then(function(newBooks) {
//     return Promise.map(newBooks, function(book) {
//       var pages = book.oldBook.pages;
//       _.forEach(pages, function(page) {
//         book.items[0].pages.push({
//           id: page.id,
//           differences: page.differences
//         });
//       });
//       return book;
//     });
//   })
//   .then(function(newBooks) {
//     return Promise.map(newBooks, function(book) {
//       var differences = book.item[0].pages[0].differences;
//     });
//   });
// }

// module.exports = function(router) {
//   router.route('/export.zip')
//     .get(function(req, res) {
//       getBooks()
//         .then(prepareBooks)
//         .then(preparePages)
//         .then(prepareZip)
//         .then(function(zip) {
//           res.send(zip);
//         });
//     });
// };
<commit_msg>Developing the export route<commit_after>var Promise = require('bluebird');
var AdmZip  = require('adm-zip');
var base    = process.env.PWD;
var rekt    = require('rekt').rekt;
var Book    = requireLocal('models/book.js');
var Page    = requireLocal('models/page.js');
var _       = require('lodash');
var fs      = require('fs');

module.exports = function(router) {
  router.route('/export.zip')
    .all(function(req, res, next) {
      if (req.token.subject === 'Tiltfactor' || req.token.subject === 'BHL') {
        next();
      } else {
        res.status(401);
        res.json({
          message: 'You don\'t have access to this.'
        });
      }
    })
    .get(function(req, res) {
      getBooks()
        .then(function(books) {
          res.send(books);
        });
    });
};

function getBooks() {
  return new Promise(function(resolve, reject) {
    Book.find({})
      .lean()
      .populate({
        path: 'pages'
      })
      .exec(function(err, pages) {
        var options = {
          path: 'pages.differences',
          model: 'Difference'
        };

        if (err) {
          reject(err)
        } else {
          Book.populate(pages, options, function(err, books) {
            if (err) {
              reject(err)
            }
            resolve(books);
          });
        }
      });
  }).then(function(books) {
    return books;
  });
}

// function getBooks() {
//   return new Promise(function(resolve, reject) {
//     Book.find({})
//       .lean()
//       .populate({
//         path: 'pages'
//       })
//       .exec(function(err, pages) {
//         var options = {
//           path: 'pages.differences',
//           model: 'Difference'
//         };

//         if (err) {
//           reject(err);
//         } else {
//           Book.populate(pages, options, function(err, books) {
//             if (err) reject(err);
//             resolve(books);
//           });
//         }
//       });
//   });
// }

// function prepareBooks(books) {
//   return Promise.map(books, function(book) {
//     return {
//       items: [{
//         id: book.id,
//         barcode: book.barcode,
//         pages: []
//       }],
//       oldBook: book
//     };
//   })
//   .then(function(newBooks) {
//     return Promise.map(newBooks, function(book) {
//       var pages = book.oldBook.pages;
//       _.forEach(pages, function(page) {
//         book.items[0].pages.push({
//           id: page.id,
//           differences: page.differences
//         });
//       });
//       return book;
//     });
//   })
//   .then(function(newBooks) {
//     return Promise.map(newBooks, function(book) {
//       var differences = book.item[0].pages[0].differences;
//     });
//   });
// }

// module.exports = function(router) {
//   router.route('/export.zip')
//     .get(function(req, res) {
//       getBooks()
//         .then(prepareBooks)
//         .then(preparePages)
//         .then(prepareZip)
//         .then(function(zip) {
//           res.send(zip);
//         });
//     });
// };
<|endoftext|>


<commit_before>'use strict'
var SourceMappingDecoder = require('ethereum-remix').util.SourceMappingDecoder
var AstWalker = require('ethereum-remix').util.AstWalker

class ContextualListener {
  constructor (api, events) {
    this._api = api
    this._index = {
      Declarations: {},
      FlatReferences: {}
    }
    this._activeHighlights = []

    events.compiler.register('compilationFinished', (success, data, source) => {
      this._stopHighlighting()
      this._index = {
        Declarations: {},
        FlatReferences: {}
      }
      if (success) {
        this._buildIndex(data, source)
      }
    })

    events.editor.register('sessionSwitched', () => { this._stopHighlighting() })
    events.editor.register('contentChanged', () => { this._stopHighlighting() })

    this.sourceMappingDecoder = new SourceMappingDecoder()
    this.astWalker = new AstWalker()
    setInterval(() => {
      this._highlightItems(api.getCursorPosition(), api.getCompilationResult())
    }, 1000)
  }

  _highlightItems (cursorPosition, compilationResult) {
    if (this.currentPosition === cursorPosition) return
    this._stopHighlighting()
    this.currentPosition = cursorPosition
    if (compilationResult && compilationResult.data && compilationResult.source) {
      var nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[compilationResult.source.target])
      if (nodes && nodes.length && nodes[nodes.length - 1]) {
        this._hightlightExpressions(nodes[nodes.length - 1], compilationResult)
      }
    }
  }

  _buildIndex (compilationResult, source) {
    if (compilationResult && compilationResult.sources) {
      var self = this
      var callback = {}
      callback['*'] = function (node) {
        if (node && node.attributes && node.attributes.referencedDeclaration) {
          if (!self._index['Declarations'][node.attributes.referencedDeclaration]) {
            self._index['Declarations'][node.attributes.referencedDeclaration] = []
          }
          self._index['Declarations'][node.attributes.referencedDeclaration].push(node)
        }
        self._index['FlatReferences'][node.id] = node
        return true
      }
      this.astWalker.walk(compilationResult.sources[source.target].AST, callback)
    }
  }

  _highlight (node, compilationResult) {
    var src = node.src
    var position = this.sourceMappingDecoder.decode(src)
    var eventId = this._api.highlight(position, node)
    if (eventId) {
      this._activeHighlights.push({ eventId, position, fileTarget: compilationResult.source.target })
    }
  }

  _hightlightExpressions (node, compilationResult) {
    var self = this
    function highlights (id) {
      if (self._index['Declarations'] && self._index['Declarations'][id]) {
        var calls = self._index['Declarations'][id]
        for (var call in calls) {
          var node = calls[call]
          self._highlight(node, compilationResult)
        }
      }
    }
    if (node.attributes && node.attributes.referencedDeclaration) {
      highlights(node.attributes.referencedDeclaration)
      var current = this._index['FlatReferences'][node.attributes.referencedDeclaration]
      this._highlight(current, compilationResult)
    } else {
      highlights(node.id)
    }
  }

  _stopHighlighting () {
    for (var event in this._activeHighlights) {
      this._api.stopHighlighting(this._activeHighlights[event])
    }
    this._activeHighlights = []
  }
}

module.exports = ContextualListener
<commit_msg>add check<commit_after>'use strict'
var SourceMappingDecoder = require('ethereum-remix').util.SourceMappingDecoder
var AstWalker = require('ethereum-remix').util.AstWalker

class ContextualListener {
  constructor (api, events) {
    this._api = api
    this._index = {
      Declarations: {},
      FlatReferences: {}
    }
    this._activeHighlights = []

    events.compiler.register('compilationFinished', (success, data, source) => {
      this._stopHighlighting()
      this._index = {
        Declarations: {},
        FlatReferences: {}
      }
      if (success) {
        this._buildIndex(data, source)
      }
    })

    events.editor.register('sessionSwitched', () => { this._stopHighlighting() })
    events.editor.register('contentChanged', () => { this._stopHighlighting() })

    this.sourceMappingDecoder = new SourceMappingDecoder()
    this.astWalker = new AstWalker()
    setInterval(() => {
      this._highlightItems(api.getCursorPosition(), api.getCompilationResult())
    }, 1000)
  }

  _highlightItems (cursorPosition, compilationResult) {
    if (this.currentPosition === cursorPosition) return
    this._stopHighlighting()
    this.currentPosition = cursorPosition
    if (compilationResult && compilationResult.data && compilationResult.source) {
      var nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[compilationResult.source.target])
      if (nodes && nodes.length && nodes[nodes.length - 1]) {
        this._hightlightExpressions(nodes[nodes.length - 1], compilationResult)
      }
    }
  }

  _buildIndex (compilationResult, source) {
    if (compilationResult && compilationResult.sources) {
      var self = this
      var callback = {}
      callback['*'] = function (node) {
        if (node && node.attributes && node.attributes.referencedDeclaration) {
          if (!self._index['Declarations'][node.attributes.referencedDeclaration]) {
            self._index['Declarations'][node.attributes.referencedDeclaration] = []
          }
          self._index['Declarations'][node.attributes.referencedDeclaration].push(node)
        }
        self._index['FlatReferences'][node.id] = node
        return true
      }
      this.astWalker.walk(compilationResult.sources[source.target].AST, callback)
    }
  }

  _highlight (node, compilationResult) {
    if (!node) return
    var position = this.sourceMappingDecoder.decode(node.src)
    var eventId = this._api.highlight(position, node)
    if (eventId) {
      this._activeHighlights.push({ eventId, position, fileTarget: compilationResult.source.target })
    }
  }

  _hightlightExpressions (node, compilationResult) {
    var self = this
    function highlights (id) {
      if (self._index['Declarations'] && self._index['Declarations'][id]) {
        var calls = self._index['Declarations'][id]
        for (var call in calls) {
          var node = calls[call]
          self._highlight(node, compilationResult)
        }
      }
    }
    if (node.attributes && node.attributes.referencedDeclaration) {
      highlights(node.attributes.referencedDeclaration)
      var current = this._index['FlatReferences'][node.attributes.referencedDeclaration]
      this._highlight(current, compilationResult)
    } else {
      highlights(node.id)
    }
  }

  _stopHighlighting () {
    for (var event in this._activeHighlights) {
      this._api.stopHighlighting(this._activeHighlights[event])
    }
    this._activeHighlights = []
  }
}

module.exports = ContextualListener
<|endoftext|>


<commit_before>/** authenticated.js
* @file: /src/app/routes/ajax/authenticated.js
* @description: Handles express routing for the authenticated ajax routes
* @parameters: Object(app), Object(resources)
* @exports: Express routes
*/

var ObjectID = require('mongodb').ObjectID

module.exports = function (app, resources) {
  /** /ajax/addVote
  * @type: GET
  * @description: Ajax route for handling user votes.
  */
  app.get('/ajax/addVote', function (req, res) {
    if(req.query.id != undefined && req.query.content != undefined) {
     if (req.isAuthenticated()) {
        var votesdb = resources.collections.votes
        var pagesdb = resources.collections.pages

        votesdb.count({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function(err, count) {
          if(count < 1) { // Användaren har inte röstat
            var isodate = resources.functions.getISOdate()
            var data = {
              content: parseInt(req.query.content),
              timestamp: isodate,
              post: { id: new ObjectID(req.query.id) },
              user: { id: req.user._id }
            }

            votesdb.insert(data, function (err) {
              if(err) throw err
              votesdb.aggregate(
              [ { $match: { "post.id": new ObjectID(req.query.id) } },
                { $group: {
                  _id: new ObjectID(req.query.id),
                  avg: { $avg: "$content" },
                }
              } ], function(err, result) {
                votesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, total_count) {
                  result[0].count = total_count
                  pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$set: { "rating.votes_sum": result[0].avg}, $inc: { "rating.votes": 1 }}, function (err) {
                    if(err) throw err
                    res.send(result)
                  })
                })
              })
            })
          } else { // Användaren har röstat
            res.send('3') // Vote already found!
          }
        })
      } else {
        res.send('1') // Not logged in
      }
    } else {
      res.send('2') // All required variables not found in url
    }
  })

  /** /ajax/like
  * @type: GET
  * @description: Ajax route for handling user likes.
  */
  app.get('/ajax/like', function (req, res) {
    if(req.query.id != undefined) {

      if (req.isAuthenticated()) {
        var likesdb = resources.collections.likes
        likesdb.count({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function (err, count) {
          if(count > 0) { // Already liked, remove it
            likesdb.remove({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function (err) {
              if (err) throw err
            })

            var pagesdb = resources.collections.pages
            pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$inc: { "rating.likes": -1, }}, function (err) {
              if(err) throw err
            })

            likesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, sum) {
              if (err) throw err
              var response = { 'action':0, 'new_value':sum }
              res.send(response)
            })
          } else { // First time pressing, add it
            var data = {
              post: { id: new ObjectID(req.query.id) },
              user: { id: req.user._id }
            }

            likesdb.insert(data, function (err) {
              if (err) throw err
            })

            var pagesdb = resources.collections.pages
            pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$inc: { "rating.likes": 1, }}, function (err) {
              if (err) throw err
            })

            likesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, sum) {
              if (err) throw err
              var response = { 'action':1, 'new_value':sum }
              res.send(response)
            })
          }
        })
      } else {
        res.redirect('/login/like')
      }
    } else {
      res.send('2') // All variables isnt set
    }
  })

  app.get('/ajax/remove/:post_id', function(req, res) {
    var post_id = req.params.post_id
    var userid = req.params.user._id

    var pagesdb = resources.collections.pages
    var usersdb = resources.collections.users

    if(typeof(req.user) !== 'undefined') {
      //Make sure user exists
      usersdb.find({_id: new ObjectID(userid)}).toArray(function(err, users) {
        if (err) throw err

        if(users.length > 0) {
          var user = users[0]

          var pageQuery = {
            _id: new ObjectID(post_id)
          }
          //TODO move this into functions.js
          var privileged = ['admin','moderator']
          if( privileged.indexOf(user.info.permission) == -1 ) {
            //Lacking privileges, check that the user is the creator of the page
            pageQuery["user_info.id"] = new ObjectID(user._id)
          }

          pagesdb.update( pageQuery, { $set: { remove: true, "timestamp.updated": functions.getISOdate() } }, function(err, status) {
            if (err) throw err
            if(status.result.n > 0) {
              res.json({
                success: status.result.nModified,
                message: (status.result.nModified) ? 'Page has already been scheduled for deletion' : 'Page has been scheduled for deletion'
              })
            } else {
              res.json({
                success: false,
                message: 'No page in database matched'
              })
            }
          })
        } else {
          res.json({
            success: false,
            message: 'Unable to locate user in database'
          })
        }
      })
    }
  })
}<commit_msg>Fix remove page submission<commit_after>/** authenticated.js
* @file: /src/app/routes/ajax/authenticated.js
* @description: Handles express routing for the authenticated ajax routes
* @parameters: Object(app), Object(resources)
* @exports: Express routes
*/

var ObjectID = require('mongodb').ObjectID

module.exports = function (app, resources) {
  var functions = resources.functions
  /** /ajax/addVote
  * @type: GET
  * @description: Ajax route for handling user votes.
  */
  app.get('/ajax/addVote', function (req, res) {
    if(req.query.id != undefined && req.query.content != undefined) {
     if (req.isAuthenticated()) {
        var votesdb = resources.collections.votes
        var pagesdb = resources.collections.pages

        votesdb.count({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function(err, count) {
          if(count < 1) { // Användaren har inte röstat
            var isodate = resources.functions.getISOdate()
            var data = {
              content: parseInt(req.query.content),
              timestamp: isodate,
              post: { id: new ObjectID(req.query.id) },
              user: { id: req.user._id }
            }

            votesdb.insert(data, function (err) {
              if(err) throw err
              votesdb.aggregate(
              [ { $match: { "post.id": new ObjectID(req.query.id) } },
                { $group: {
                  _id: new ObjectID(req.query.id),
                  avg: { $avg: "$content" },
                }
              } ], function(err, result) {
                votesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, total_count) {
                  result[0].count = total_count
                  pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$set: { "rating.votes_sum": result[0].avg}, $inc: { "rating.votes": 1 }}, function (err) {
                    if(err) throw err
                    res.send(result)
                  })
                })
              })
            })
          } else { // Användaren har röstat
            res.send('3') // Vote already found!
          }
        })
      } else {
        res.send('1') // Not logged in
      }
    } else {
      res.send('2') // All required variables not found in url
    }
  })

  /** /ajax/like
  * @type: GET
  * @description: Ajax route for handling user likes.
  */
  app.get('/ajax/like', function (req, res) {
    if(req.query.id != undefined) {

      if (req.isAuthenticated()) {
        var likesdb = resources.collections.likes
        likesdb.count({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function (err, count) {
          if(count > 0) { // Already liked, remove it
            likesdb.remove({ "post.id": new ObjectID(req.query.id), "user.id": req.user._id }, function (err) {
              if (err) throw err
            })

            var pagesdb = resources.collections.pages
            pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$inc: { "rating.likes": -1, }}, function (err) {
              if(err) throw err
            })

            likesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, sum) {
              if (err) throw err
              var response = { 'action':0, 'new_value':sum }
              res.send(response)
            })
          } else { // First time pressing, add it
            var data = {
              post: { id: new ObjectID(req.query.id) },
              user: { id: req.user._id }
            }

            likesdb.insert(data, function (err) {
              if (err) throw err
            })

            var pagesdb = resources.collections.pages
            pagesdb.update({ "_id": new ObjectID(req.query.id) }, {$inc: { "rating.likes": 1, }}, function (err) {
              if (err) throw err
            })

            likesdb.count({ "post.id": new ObjectID(req.query.id) }, function (err, sum) {
              if (err) throw err
              var response = { 'action':1, 'new_value':sum }
              res.send(response)
            })
          }
        })
      } else {
        res.redirect('/login/like')
      }
    } else {
      res.send('2') // All variables isnt set
    }
  })

  app.get('/ajax/remove/:post_id', function(req, res) {
    var post_id = req.params.post_id
    var userid = req.user._id

    var pagesdb = resources.collections.pages
    var usersdb = resources.collections.users

    if(typeof(req.user) !== 'undefined') {
      //Make sure user exists
      usersdb.find({_id: new ObjectID(userid)}).toArray(function(err, users) {
        if (err) throw err

        if(users.length > 0) {
          var user = users[0]

          var pageQuery = {
            _id: new ObjectID(post_id)
          }
          //TODO move this into functions.js
          var privileged = ['admin','moderator']
          if( privileged.indexOf(user.info.permission) == -1 ) {
            //Lacking privileges, check that the user is the creator of the page
            pageQuery["user_info.id"] = new ObjectID(user._id)
          }

          pagesdb.update( pageQuery, { $set: { remove: true, "timestamp.updated": functions.getISOdate() } }, function(err, status) {
            if (err) throw err
            if(status.result.n > 0) {
              res.json({
                success: status.result.nModified,
                message: (status.result.nModified) ? 'Page has already been scheduled for deletion' : 'Page has been scheduled for deletion'
              })
            } else {
              res.json({
                success: false,
                message: 'No page in database matched'
              })
            }
          })
        } else {
          res.json({
            success: false,
            message: 'Unable to locate user in database'
          })
        }
      })
    }
  })
}<|endoftext|>


<commit_before>class Observable {

    constructor(start) {

        // The stream initializer must be a function
        if (typeof start !== "function")
            throw new TypeError("Observer definition is not a function");

        this._start = start;
    }

    observe(sink) {

        // The sink must be an object
        if (Object(sink) !== sink)
            throw new TypeError("Sink is not an object");

        var start = this._start,
            finished = false,
            cleanup;

        // Wrap the provided sink
        sink = _wrapSink(sink, _=> {

            finished = true;

            if (cleanup !== void 0)
                cleanup();
        });

        try {

            // Call the stream initializer.  The initializer will return a cleanup
            // function or undefined.
            cleanup = start.call(void 0, sink);

        } catch (e) {

            sink.throw(e);
        }

        // If the stream is already finished, then perform cleanup
        if (finished && cleanup !== void 0)
            cleanup();

        // Return a cancelation function
        return sink.return;
    }

    /*

        Observer sinks are wrapped for the following reasons:

        - Ensures that the sink is not called after the stream is closed.
        - Ensures that the returned object has all three sink methods ("next", "throw", and "return").
        - Ensures that values are properly handled when the sink does not have "throw" or "return".
        - Ensures that returned methods can be called without a provided "this" value.
        - Ensures that cleanup is triggered when the stream is closed.

    */
    function _wrapSink(sink, cleanup) {

        var done = false;

        // Marks the stream as closed and triggers stream cleanup.  Exceptions
        // which occur during cleanup are propagated to the caller.
        function close() {

            if (!done) {

                done = true;
                cleanup();
            }
        }

        // Returns a "done" result
        function doneResult() {

            return { value: void 0, done: true };
        }

        // Sends a completion value to the sink
        function send(op, value) {

            // If the stream if closed, then return a "done" result
            if (done)
                return doneResult();

            var result;

            try {

                switch (op) {

                    case "next":

                        // Send the next value to the sink
                        result = sink.next(value);
                        break;

                    case "throw":

                        // If the sink does not support "throw", then throw value back to caller
                        if (!("throw" in sink))
                            throw value;

                        result = sink.throw(value);
                        break;

                    case "return":

                        // If the sink does not support "return", then close and return a done result
                        if (!("return" in sink))
                            return close(), doneResult();

                        result = sink.return(value);

                        // If the sink does not return a result, then assume that it is finished
                        if (!result)
                            result = doneResult();

                        break;

                }

            } catch (e) {

                // If the sink throws, then close the stream and throw error to caller
                close();
                throw e;
            }

            // If the sink is finished receiving data, then close the stream
            if (result && result.done)
                close();

            return result;
        }

        return {
            next(value) { return send("next", value) },
            throw(value) { return send("throw", value) },
            return(value) { return send("return", value) },
        };
    }

    forEach(fn) {

        return new Promise((resolve, reject) => {

            this.observe({

                next: fn,
                throw: reject,
                return: resolve,
            });
        });
    }

    map(fn) {

        if (typeof fn !== "function")
            throw new TypeError("Callback is not a function");

        return new this.constructor(sink => this.observe({

            next(value) {

                var value;

                try { value = fn(value) }
                catch (e) { return sink.throw(e) }

                return sink.next(value);
            },

            throw: sink.throw,
            return: sink.return,

        }));
    }

    [Symbol.asyncIterator]() {

        var pending = [], ready = [];

        function current() {

            if (pending.length > 0)
                return pending.shift();

            var d;
            ready.push(new Promise((resolve, reject) => d = { resolve, reject }));
            return d;
        }

        var cancel = this.observe({

            next(value) { current().resolve({ value, done: false }) },
            throw(value) { current().reject(value) },
            return(value) { current().resolve({ value, done: true }) },
        });

        return {

            next() {

                if (ready.length > 0)
                    return ready.shift();

                return new Promise((resolve, reject) => pending.push({ resolve, reject }));
            },

            throw(value) {

                return new Promise((resolve, reject) => {

                    cancel();
                    reject(value);
                });
            },

            return() {

                return new Promise(resolve => {

                    cancel();
                    resolve({ value: void 0, done: true });
                });
            },
        };
    }

}

if (!_esdown.global.Observable)
    _esdown.global.Observable = Observable;
<commit_msg>Allow a function argument to Observable.prototype.observe.<commit_after>class Observable {

    constructor(start) {

        // The stream initializer must be a function
        if (typeof start !== "function")
            throw new TypeError("Observer definition is not a function");

        this._start = start;
    }

    observe(sink) {

        if (typeof sink === "function")
            sink = { next: sink };

        // The sink must be an object
        if (Object(sink) !== sink)
            throw new TypeError("Sink is not an object");

        var start = this._start,
            finished = false,
            cleanup;

        // Wrap the provided sink
        sink = _wrapSink(sink, _=> {

            finished = true;

            if (cleanup !== void 0)
                cleanup();
        });

        try {

            // Call the stream initializer.  The initializer will return a cleanup
            // function or undefined.
            cleanup = start.call(void 0, sink);

        } catch (e) {

            sink.throw(e);
        }

        // If the stream is already finished, then perform cleanup
        if (finished && cleanup !== void 0)
            cleanup();

        // Return a cancelation function
        return sink.return;
    }

    /*

        Observer sinks are wrapped for the following reasons:

        - Ensures that the sink is not called after the stream is closed.
        - Ensures that the returned object has all three sink methods ("next", "throw", and "return").
        - Ensures that values are properly handled when the sink does not have "throw" or "return".
        - Ensures that returned methods can be called without a provided "this" value.
        - Ensures that cleanup is triggered when the stream is closed.

    */
    function _wrapSink(sink, cleanup) {

        var done = false;

        // Marks the stream as closed and triggers stream cleanup.  Exceptions
        // which occur during cleanup are propagated to the caller.
        function close() {

            if (!done) {

                done = true;
                cleanup();
            }
        }

        // Returns a "done" result
        function doneResult() {

            return { value: void 0, done: true };
        }

        // Sends a completion value to the sink
        function send(op, value) {

            // If the stream if closed, then return a "done" result
            if (done)
                return doneResult();

            var result;

            try {

                switch (op) {

                    case "next":

                        // Send the next value to the sink
                        result = sink.next(value);
                        break;

                    case "throw":

                        // If the sink does not support "throw", then throw value back to caller
                        if (!("throw" in sink))
                            throw value;

                        result = sink.throw(value);
                        break;

                    case "return":

                        // If the sink does not support "return", then close and return a done result
                        if (!("return" in sink))
                            return close(), doneResult();

                        result = sink.return(value);

                        // If the sink does not return a result, then assume that it is finished
                        if (!result)
                            result = doneResult();

                        break;

                }

            } catch (e) {

                // If the sink throws, then close the stream and throw error to caller
                close();
                throw e;
            }

            // If the sink is finished receiving data, then close the stream
            if (result && result.done)
                close();

            return result;
        }

        return {
            next(value) { return send("next", value) },
            throw(value) { return send("throw", value) },
            return(value) { return send("return", value) },
        };
    }

    forEach(fn) {

        return new Promise((resolve, reject) => {

            this.observe({

                next: fn,
                throw: reject,
                return: resolve,
            });
        });
    }

    map(fn) {

        if (typeof fn !== "function")
            throw new TypeError("Callback is not a function");

        return new this.constructor(sink => this.observe({

            next(value) {

                var value;

                try { value = fn(value) }
                catch (e) { return sink.throw(e) }

                return sink.next(value);
            },

            throw: sink.throw,
            return: sink.return,

        }));
    }

    [Symbol.asyncIterator]() {

        var pending = [], ready = [];

        function current() {

            if (pending.length > 0)
                return pending.shift();

            var d;
            ready.push(new Promise((resolve, reject) => d = { resolve, reject }));
            return d;
        }

        var cancel = this.observe({

            next(value) { current().resolve({ value, done: false }) },
            throw(value) { current().reject(value) },
            return(value) { current().resolve({ value, done: true }) },
        });

        return {

            next() {

                if (ready.length > 0)
                    return ready.shift();

                return new Promise((resolve, reject) => pending.push({ resolve, reject }));
            },

            throw(value) {

                return new Promise((resolve, reject) => {

                    cancel();
                    reject(value);
                });
            },

            return() {

                return new Promise(resolve => {

                    cancel();
                    resolve({ value: void 0, done: true });
                });
            },
        };
    }

}

if (!_esdown.global.Observable)
    _esdown.global.Observable = Observable;
<|endoftext|>