<commit_before>/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

var sentry = require('../lib/sentry');

module.exports = function (app, options) {
    "use strict";

    var underscore = require('underscore');
    var userGroupsFetcher = options.userGroupsFetcher;
    var restProxy = options.restProxy;
    var NodeCache = require('node-cache');
    var cache = new NodeCache();
    var TurbasenAuth = require('turbasen-auth');

    var turbasenAuthClient = new TurbasenAuth('Turadmin', process.env.NTB_API_KEY, {env: process.env.NTB_API_ENV || 'dev'});

    /*
     * All GET requests to /grupper
     */
    var getGroupsAll = function (req, res, next) {

        if (req.session.user.er_admin) {
            req.renderOptions = req.renderOptions || {};
            req.renderOptions.userData = JSON.stringify(req.session.user);
            req.renderOptions.userGroups = JSON.stringify(req.userGroups || []);
            req.renderOptions.routeApiUri = options.routeApiUri;
            req.renderOptions.authType = req.session.authType;
            req.renderOptions.isAdmin = req.session.user.er_admin;
            next();

        } else {
            res.redirect(401, '/turer');
        }

    };

    /**
     * GET list of groups (index page)
     */
    var getGroupsIndex = function (req, res, next) {

        var userGroups = req.userGroups || [];
        var userDefaultGroupFetchQuery = (!!req.signedCookies && req.signedCookies['userDefaultGroupFetchQuery_' + req.session.userId]) ? req.signedCookies['userDefaultGroupFetchQuery_' + req.session.userId] : {};

        var groups;
        var onCompleteGrupperRequest = function (data) {
            // cache.set('/grupper/?tags=!&limit=200&fields=navn&sort=navn', data, 86400);
            groups = data.documents;
            onCompleteNtbRequest();
        };

        var onCompleteNtbRequest = underscore.after(1, function () {
            var renderOptions = {
                title: 'Mine grupper',
                userData: JSON.stringify(req.session.user),
                userGroups: JSON.stringify(userGroups),
                externalGroups: JSON.stringify(groups),
                authType: req.session.authType,
                itemType: 'grupper',
                isAdmin: req.session.user.er_admin
            };

            res.render('groups/index', renderOptions);
        });

        restProxy.makeApiRequest('/grupper/?tags=!DNT&limit=200&fields=navn&sort=navn', req, undefined, onCompleteGrupperRequest);

    };

    var getGroupsNew = function (req, res, next) {
        req.renderOptions = req.renderOptions || {};
        req.renderOptions.title = 'Ny gruppe';
        res.render('groups/editor', req.renderOptions);
    };

    var getGroupsEdit = function (req, res, next) {
        // Id of group being edited
        var groupId = req.params.id;

        // Callback for when group data is fetched from NTB
        var onCompleteGroupRequest = function (data) {

            var groupData = data;

            req.renderOptions = req.renderOptions || {};
            req.renderOptions.title = groupData.navn;
            req.renderOptions.groupName = groupData.navn;
            req.renderOptions.groupData = JSON.stringify(groupData);

            res.render('groups/editor', req.renderOptions);

        };

        restProxy.makeApiRequest('/grupper/' + groupId, req, undefined, onCompleteGroupRequest);
    };

    var postPutGroups = function (req, res, next) {
        var group = req.body;
        var users = group && group.privat && group.privat.brukere || [];
        var uri = '/grupper';
        uri += (!!req.params.id) ? '/' + req.params.id : '';

        var saveGroup = function () {
            restProxy.makeApiRequest(uri, req, res, undefined);
        };

        var passwordIsPbkdf2 = underscore.after(users.length, function () {
            saveGroup();
        });

        var userAuthCreated = function (err, user) {
            if (err) {
                sentry.captureError(err, {extra: {query: req.query}});

            } else {
                var userIndex = users.indexOf(underscore.findWhere(users, {epost: user.epost}));
                users[userIndex] = user;
                passwordIsPbkdf2();
            }
        };

        if (users && users.length > 0) {

            for (var i = 0, user; i < users.length; i++) {
                user = users[i];

                // If new password is set for any of the users, generate pbkdf2
                if (user.passord) {
                    turbasenAuthClient.createUserAuth(user.navn, user.epost, user.passord, userAuthCreated);

                } else {
                    passwordIsPbkdf2();
                }
            }

        } else {
            saveGroup();
        }

    };

    app.get('/grupper*', userGroupsFetcher);
    app.get('/grupper*', getGroupsAll);
    app.get('/grupper', getGroupsIndex);
    app.get('/grupper/ny', getGroupsNew);
    app.get('/grupper/:id', getGroupsEdit);

    app.post('/ntb-api/grupper', postPutGroups);
    app.put('/ntb-api/grupper/:id', postPutGroups);

};
<commit_msg>Do not allow editing groups with `DNT` tag as they are managed in Sherpa<commit_after>/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

var sentry = require('../lib/sentry');

module.exports = function (app, options) {
    "use strict";

    var underscore = require('underscore');
    var userGroupsFetcher = options.userGroupsFetcher;
    var restProxy = options.restProxy;
    var NodeCache = require('node-cache');
    var cache = new NodeCache();
    var TurbasenAuth = require('turbasen-auth');

    var turbasenAuthClient = new TurbasenAuth('Turadmin', process.env.NTB_API_KEY, {env: process.env.NTB_API_ENV || 'dev'});

    /*
     * All GET requests to /grupper
     */
    var getGroupsAll = function (req, res, next) {

        if (req.session.user.er_admin) {
            req.renderOptions = req.renderOptions || {};
            req.renderOptions.userData = JSON.stringify(req.session.user);
            req.renderOptions.userGroups = JSON.stringify(req.userGroups || []);
            req.renderOptions.routeApiUri = options.routeApiUri;
            req.renderOptions.authType = req.session.authType;
            req.renderOptions.isAdmin = req.session.user.er_admin;
            next();

        } else {
            res.redirect(401, '/turer');
        }

    };

    /**
     * GET list of groups (index page)
     */
    var getGroupsIndex = function (req, res, next) {

        var userGroups = req.userGroups || [];
        var userDefaultGroupFetchQuery = (!!req.signedCookies && req.signedCookies['userDefaultGroupFetchQuery_' + req.session.userId]) ? req.signedCookies['userDefaultGroupFetchQuery_' + req.session.userId] : {};

        var groups;
        var onCompleteGrupperRequest = function (data) {
            // cache.set('/grupper/?tags=!&limit=200&fields=navn&sort=navn', data, 86400);
            groups = data.documents;
            onCompleteNtbRequest();
        };

        var onCompleteNtbRequest = underscore.after(1, function () {
            var renderOptions = {
                title: 'Mine grupper',
                userData: JSON.stringify(req.session.user),
                userGroups: JSON.stringify(userGroups),
                externalGroups: JSON.stringify(groups),
                authType: req.session.authType,
                itemType: 'grupper',
                isAdmin: req.session.user.er_admin
            };

            res.render('groups/index', renderOptions);
        });

        restProxy.makeApiRequest('/grupper/?tags=!DNT&limit=200&fields=navn&sort=navn', req, undefined, onCompleteGrupperRequest);

    };

    var getGroupsNew = function (req, res, next) {
        req.renderOptions = req.renderOptions || {};
        req.renderOptions.title = 'Ny gruppe';
        res.render('groups/editor', req.renderOptions);
    };

    var getGroupsEdit = function (req, res, next) {
        // Id of group being edited
        var groupId = req.params.id;

        // Callback for when group data is fetched from NTB
        var onCompleteGroupRequest = function (data) {

            var groupData = data;

            if ((typeof groupData.tags === 'undefined') || (groupData.tags.indexOf('DNT') === -1)) {

                req.renderOptions = req.renderOptions || {};
                req.renderOptions.title = groupData.navn;
                req.renderOptions.groupName = groupData.navn;
                req.renderOptions.groupData = JSON.stringify(groupData);

                res.render('groups/editor', req.renderOptions);

            } else {
                res.redirect(403, '/grupper');
            }

        };

        restProxy.makeApiRequest('/grupper/' + groupId, req, undefined, onCompleteGroupRequest);
    };

    var postPutGroups = function (req, res, next) {
        var group = req.body;
        var users = group && group.privat && group.privat.brukere || [];
        var uri = '/grupper';
        uri += (!!req.params.id) ? '/' + req.params.id : '';

        var saveGroup = function () {
            restProxy.makeApiRequest(uri, req, res, undefined);
        };

        var passwordIsPbkdf2 = underscore.after(users.length, function () {
            saveGroup();
        });

        var userAuthCreated = function (err, user) {
            if (err) {
                sentry.captureError(err, {extra: {query: req.query}});

            } else {
                var userIndex = users.indexOf(underscore.findWhere(users, {epost: user.epost}));
                users[userIndex] = user;
                passwordIsPbkdf2();
            }
        };

        if (users && users.length > 0) {

            for (var i = 0, user; i < users.length; i++) {
                user = users[i];

                // If new password is set for any of the users, generate pbkdf2
                if (user.passord) {
                    turbasenAuthClient.createUserAuth(user.navn, user.epost, user.passord, userAuthCreated);

                } else {
                    passwordIsPbkdf2();
                }
            }

        } else {
            saveGroup();
        }

    };

    app.get('/grupper*', userGroupsFetcher);
    app.get('/grupper*', getGroupsAll);
    app.get('/grupper', getGroupsIndex);
    app.get('/grupper/ny', getGroupsNew);
    app.get('/grupper/:id', getGroupsEdit);

    app.post('/ntb-api/grupper', postPutGroups);
    app.put('/ntb-api/grupper/:id', postPutGroups);

};
<|endoftext|>


<commit_before>'use strict';
var angular = require('angular');
/**
 * @ngInject
 */
var ExpeditionSearchController = function ($scope, $location, $controller, Expedition, npdcAppConfig) {

  $controller('NpolarBaseController', { $scope: $scope });
  $scope.resource = Expedition;

  npdcAppConfig.search.local.results.detail = (e) => {
    return "Activity start: " + e.activity[0].departed.split('T')[0];
   };

  npdcAppConfig.cardTitle = "Expedition Archive";
  npdcAppConfig.search.local.results.subtitle = "type";
  npdcAppConfig.search.local.filterUi = {
    'year-activity.departed': {
      type: 'range'
    },
    'updated': {
      type: 'hidden'
    }
  };

  let query = function() {
    let defaults = { limit: "all", sort: "-updated", fields: 'code,id,updated,type,activity.departed',
      'date-year': 'activity.departed', facets: 'tags,people.email,updated,locations.area' };
    let invariants = $scope.security.isAuthenticated() ? {} : {} ;
    return angular.extend(defaults, $location.search(), invariants);
  };

  $scope.search(query());

  $scope.$on('$locationChangeSuccess', (event, data) => {
    $scope.search(query());
  });

};

module.exports = ExpeditionSearchController;
<commit_msg>Updated search<commit_after>'use strict';
/**
 * @ngInject
 */
var ExpeditionSearchController = function ($scope, $location, $controller, Expedition, npdcAppConfig) {

  $controller('NpolarBaseController', { $scope: $scope });
  $scope.resource = Expedition;

  npdcAppConfig.search.local.results.detail = (e) => {
    return "Activity start: " + e.activity[0].departed.split('T')[0];
   };

  npdcAppConfig.cardTitle = "Expedition Archive";
  npdcAppConfig.search.local.results.subtitle = "type";
  npdcAppConfig.search.local.filterUi = {
    'year-activity.departed': {
      type: 'range'
    },
    'updated': {
      type: 'hidden'
    }
  };

  let query = function() {
    let defaults = { limit: "all", sort: "-updated", fields: 'code,id,updated,type,activity.departed',
      'date-year': 'activity.departed', facets: 'tags,people.email,updated,locations.area' };
    let invariants = $scope.security.isAuthenticated() ? {} : {} ;
    return Object.assign({}, defaults, $location.search(), invariants);
  };

  $scope.search(query());

  $scope.$on('$locationChangeSuccess', (event, data) => {
    $scope.search(query());
  });

};

module.exports = ExpeditionSearchController;
<|endoftext|>


<commit_before>var express     = require('express'),
    router      = express.Router(),
    api         = express.Router(),
    r           = require('../util').routing;

router.get('/overview',          render('panel/server'));
router.get('/packages/install',  render('panel/server/packages/install'));
router.get('/packages/update',   render('panel/server/packages/update'));
router.get('/packages',          render('panel/server/packages/list'));
router.get('/packages/view',      render('panel/server/packages'));
router.get('/services',          render('panel/server/services/list'));
router.get('/services/view',      render('panel/server/services'));
router.get('/processes',         reserved);
router.get('/processes/view',      reserved);

api.get('/:hostname/', function(req, res) {
    r.findServer({ hostname: req.params.hostname }, function(result) {
        if (!!result) return res.send({
                hostname: req.params.hostname,
                type    : result.type,
                host    : result.host,
                port    : result.port
            });
        else return res.send('Result not found');
    });
});

function render(path) {
    return function(req, res) {
        res.render(path);
    };
}

function reserved(req, res) {
    res.send('This subdirectory is reserved for future use');
}

module.exports = router;
module.exports.api = api;
<commit_msg>Spacing cleanup<commit_after>var express     = require('express'),
    router      = express.Router(),
    api         = express.Router(),
    r           = require('../util').routing;

router.get('/overview',          render('panel/server'));
router.get('/packages/install',  render('panel/server/packages/install'));
router.get('/packages/update',   render('panel/server/packages/update'));
router.get('/packages',          render('panel/server/packages/list'));
router.get('/packages/view',     render('panel/server/packages'));
router.get('/services',          render('panel/server/services/list'));
router.get('/services/view',     render('panel/server/services'));
router.get('/processes',         reserved);
router.get('/processes/view',    reserved);

api.get('/:hostname/', function(req, res) {
    r.findServer({ hostname: req.params.hostname }, function(result) {
        if (!!result) {
            return res.send({
                hostname: req.params.hostname,
                type    : result.type,
                host    : result.host,
                port    : result.port
            });
        } else return res.send('Result not found');
    });
});

function render(path) {
    return function(req, res) {
        res.render(path);
    };
}

function reserved(req, res) {
    res.send('This subdirectory is reserved for future use');
}

module.exports = router;
module.exports.api = api;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'

// TODO: use https://github.com/reactjs/react-magic/blob/master/README-htmltojsx.md

class Page extends Component {
    componentDidMount() {
        const { selectPage, fetchPage } = this.props

        selectPage(this.props.params.splat)
        fetchPage(this.props.params.splat)
    }

    getHtml() {
        if (!this.props.pages[this.props.selectedPage]) {
            return {
                __html: ''
            }
        }

        let doc = (new DOMParser()).parseFromString(this.props.pages[this.props.selectedPage].data.text, 'text/html')

        // Convert all links pointing to JSON-LD documents
        let anchors = doc.querySelectorAll('a')
        for (let i = 0; i < anchors.length; i++) {
            let href = anchors[i].getAttribute('href')

            if (/^(?:[a-z]+:)?\/\//i.test(href)) {
                // Make absolute URLs in target blank
                anchors[i].setAttribute('target', '_blank')
            } else {
                // Convert relative JSON-LD URLs
                anchors[i].setAttribute('href', href.replace(/index\.jsonld/, '').replace(/\.jsonld/, ''))
            }
        }

        // Convert all images
        let images = doc.querySelectorAll('img')
        for (let i = 0; i < images.length; i++) {
            let src = images[i].getAttribute('src');

            // Convert relative URLs
            if (!/^(?:[a-z]+:)?\/\//i.test(src)) {
                images[i].setAttribute('src', '/data/' + src)
            }
        }

        return {
            __html: doc.getElementsByTagName('body')[0].innerHTML
        }
    }

    render() {
        return (

                {/* The HTML is properly sanitized by the Go JSON-LD generator. */}

        )
    }
}

Page.propTypes = {
    selectedPage: PropTypes.string.isRequired,
    pages: PropTypes.object
}

export default Page
<commit_msg>fix() “undefined” flash during loading.<commit_after>import React, { Component, PropTypes } from 'react'

// TODO: use https://github.com/reactjs/react-magic/blob/master/README-htmltojsx.md

class Page extends Component {
    componentDidMount() {
        const { selectPage, fetchPage } = this.props

        selectPage(this.props.params.splat)
        fetchPage(this.props.params.splat)
    }

    getHtml() {
        if (!this.props.pages[this.props.selectedPage] || this.props.pages[this.props.selectedPage].isFetching) {
            return {
                __html: ''
            }
        }

        let doc = (new DOMParser()).parseFromString(this.props.pages[this.props.selectedPage].data.text, 'text/html')

        // Convert all links pointing to JSON-LD documents
        let anchors = doc.querySelectorAll('a')
        for (let i = 0; i < anchors.length; i++) {
            let href = anchors[i].getAttribute('href')

            if (/^(?:[a-z]+:)?\/\//i.test(href)) {
                // Make absolute URLs in target blank
                anchors[i].setAttribute('target', '_blank')
            } else {
                // Convert relative JSON-LD URLs
                anchors[i].setAttribute('href', href.replace(/index\.jsonld/, '').replace(/\.jsonld/, ''))
            }
        }

        // Convert all images
        let images = doc.querySelectorAll('img')
        for (let i = 0; i < images.length; i++) {
            let src = images[i].getAttribute('src');

            // Convert relative URLs
            if (!/^(?:[a-z]+:)?\/\//i.test(src)) {
                images[i].setAttribute('src', '/data/' + src)
            }
        }

        return {
            __html: doc.getElementsByTagName('body')[0].innerHTML
        }
    }

    render() {
        return (

                {/* The HTML is properly sanitized by the Go JSON-LD generator. */}

        )
    }
}

Page.propTypes = {
    selectedPage: PropTypes.string.isRequired,
    pages: PropTypes.object
}

export default Page
<|endoftext|>


<commit_before>'use strict';

function Encoder() {
  this.buffer_ = [];
  this.bitIndex_ = 0;
}

Encoder.prototype.encodeOctet = function(o) {
  this.buffer_.push(o & 0xff);
};

Encoder.prototype.encodeInteger = function(firstOctetMask, N, I) {
  var nextMarker = (1 << N) - 1;

  if (I < nextMarker) {
    this.encodeOctet(firstOctetMask | I);
    return;
  }

  if (N > 0) {
    this.encodeOctet(firstOctetMask | nextMarker);
  }

  I -= nextMarker;
  while (I >= 128) {
    this.encodeOctet(I % 128 | 128);
    I /= 128;
  }
  this.encodeOctet(I);
}

Encoder.prototype.encodeASCIIString = function(str) {
  this.encodeInteger(0, 0, str.length);
  for (var i = 0; i < str.length; ++i) {
    this.encodeOctet(str.charCodeAt(i));
  }
}

Encoder.prototype.encodeIndexedHeader = function(index) {
  var opCode = 0x80;
  var prefixLength = 7;
  this.encodeInteger(opCode, prefixLength, index);
}

Encoder.prototype.encodeLiteralHeaderWithoutIndexing = function(
  indexOrName, value) {
  var opCode = 0x60;
  var prefixLength = 5;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}

Encoder.prototype.encodeLiteralHeaderWithIncrementalIndexing = function(
  indexOrName, value) {
  var opCode = 0x20;
  var prefixLength = 5;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}

Encoder.prototype.encodeLiteralHeaderWithSubstitutionIndexing = function(
  indexOrName, substitutedIndex, value) {
  var opCode = 0x0;
  var prefixLength = 6;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeInteger(0, 0, substitutedIndex);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeInteger(0, 0, substitutedIndex);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}
<commit_msg>Implement HeaderTable.tryAppendEntry()<commit_after>'use strict';

function Encoder() {
  this.buffer_ = [];
  this.bitIndex_ = 0;
}

Encoder.prototype.encodeOctet = function(o) {
  this.buffer_.push(o & 0xff);
};

Encoder.prototype.encodeInteger = function(firstOctetMask, N, I) {
  var nextMarker = (1 << N) - 1;

  if (I < nextMarker) {
    this.encodeOctet(firstOctetMask | I);
    return;
  }

  if (N > 0) {
    this.encodeOctet(firstOctetMask | nextMarker);
  }

  I -= nextMarker;
  while (I >= 128) {
    this.encodeOctet(I % 128 | 128);
    I /= 128;
  }
  this.encodeOctet(I);
}

Encoder.prototype.encodeASCIIString = function(str) {
  this.encodeInteger(0, 0, str.length);
  for (var i = 0; i < str.length; ++i) {
    this.encodeOctet(str.charCodeAt(i));
  }
}

Encoder.prototype.encodeIndexedHeader = function(index) {
  var opCode = 0x80;
  var prefixLength = 7;
  this.encodeInteger(opCode, prefixLength, index);
}

Encoder.prototype.encodeLiteralHeaderWithoutIndexing = function(
  indexOrName, value) {
  var opCode = 0x60;
  var prefixLength = 5;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}

Encoder.prototype.encodeLiteralHeaderWithIncrementalIndexing = function(
  indexOrName, value) {
  var opCode = 0x20;
  var prefixLength = 5;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}

Encoder.prototype.encodeLiteralHeaderWithSubstitutionIndexing = function(
  indexOrName, substitutedIndex, value) {
  var opCode = 0x0;
  var prefixLength = 6;
  switch (typeof indexOrName) {
    case 'number':
      this.encodeInteger(opCode, prefixLength, indexOrName + 1);
      this.encodeInteger(0, 0, substitutedIndex);
      this.encodeASCIIString(value);
      return;

    case 'string':
      this.encodeInteger(opCode, prefixLength, 0);
      this.encodeASCIIString(indexOrName);
      this.encodeInteger(0, 0, substitutedIndex);
      this.encodeASCIIString(value);
      return;
  }

  throw new Error('not an index or name: ' + indexOrName);
}

function HeaderTable() {
  this.entries_ = [];
  this.size_ = 0;
  this.maxSize_ = 4096;
}

HeaderTable.prototype.removeFirstEntry_ = function() {
  var firstEntry = this.entries_.shift();
  this.size_ -= firstEntry.name.length + firstEntry.value.length + 32;
}

HeaderTable.prototype.tryAppendEntry = function(name, value) {
  var sizeDelta = name.length + value.length + 32;
  while (this.entries_.length > 0 && this.size_ + sizeDelta > this.maxSize_) {
    this.removeFirstEntry_();
  }
  if (this.size_ + sizeDelta <= this.maxSize_) {
    this.size_ += sizeDelta;
    this.entries_.push({ name: name, value: value });
  }
}
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs');
var _ = require('lodash');
var yeoman = require('yeoman-generator');

var CONFIG_FILE = "generator-play.json";

var DEFAULT_CONFIG = {
	global: {
    emptyBrackets: '()'
  },
  app: {
    name: '',
    version: '0.0.1-SNAPSHOT',
    playVersion: '2.1.2',
    language: '',
    secret: '',
    langs: [],
    dependencies: []
  }
};

var PATH_ROOT = process.cwd();

var PlayBase = module.exports =  function PlayBase(args, options) {
  this.logLevel = options["log-level"] || (options.verbose && "debug") || "info";

  yeoman.generators.Base.apply(this, arguments);

  // Set paths
  this.paths = {
    root: PATH_ROOT,
    app: path.join(PATH_ROOT, '/app'),
    conf: path.join(PATH_ROOT, '/conf'),
    public: path.join(PATH_ROOT, '/public'),
    resources: path.join(PATH_ROOT, '/resources'),
    project: path.join(PATH_ROOT, '/project'),
    test: path.join(PATH_ROOT, '/test')
  };

  // Extend the Yeoman Generator Base
  this.pkg = this.readFileAsJson(path.join(this.paths.root, '/package.json'), {});
  this.config = this.readConfig();

  _.mixin({
    capitalize: function (string) {
      return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
    }
  });

  this.on('end', function () {
    this.writeConfig();
  });

};

// Generic methods
yeoman.generators.Base.prototype.exists = function (val) {
	return !_.isNull(val) && !_.isUndefined(val);
};

yeoman.generators.Base.prototype.existsFile = function (path) {
	return fs.existsSync(path);
};

yeoman.generators.Base.prototype.readFileAsJson = function(path, defaultValue) {
	return this.existsFile(path) && JSON.parse(this.readFileAsString(path)) || defaultValue;
};

yeoman.generators.Base.prototype.writeFileFromJson = function(path, value, space) {
  space = space || "  ";
  return this.writeFileFromString(JSON.stringify(value, null, space), path);
};

yeoman.generators.Base.prototype.promptLoop = function (prompts, done) {
  var self = this;
  var results = [];
  var isEnded = false;
  var isMultiple = _.isArray(prompts);
  prompts = isMultiple ? prompts : [prompts];

  var cb = self.async();

  // Return true if the value should stop the loop
  // Possible values are null, undefined or empty string
  // Otherwise, return false
  function isQuitAnswer (promptValue) {
    return _.isUndefined(promptValue) || _.isNull(promptValue) || promptValue === "";
  };

  // Handle a prompt by updating the answer with its value
  // or end the loop
  function handlePrompt (prompt, props, answer) {
    var promptName = prompt.name;
    var promptValue = props[promptName];
    self.print(promptName, promptValue);

    if (isQuitAnswer(promptValue)) {
      self.debug(">> End prompt loop because of empty answer");
      isEnded = true;
    } else {
      answer[promptName] = promptValue;
    }
  };

  // Perform one iteration of the loop.
  // Depending on the answer(s), will call itself
  // to iterate one more step or stop the loop.
  function ask () {
    self.prompt(prompts, function (props, err) {
      if (err) {
        self.debug(">> End prompt loop because of error: ", err);
        isEnded = true;
        done(results, err);
      }

      var answer = {};

      _.map(prompts, function (prompt) {
        handlePrompt(prompt, props, answer);
      });

      if (!isEnded) {
        results.push(isMultiple ? answer : answer[prompts[0].name]);
        self.debug("Ask one more time. Current status: ", results);
        ask();
      } else {
        self.debug("End of prompt loop. Final status: ", results);
        done(results, null);
      }
    }.bind(self));
  };

  // Ask at least one time the prompts
  ask();
};

yeoman.generators.Base.prototype.appendLine = function (contentString, newLine) {
  return contentString + "\n" + newLine;
};

yeoman.generators.Base.prototype.mustacheEngine = function (text, data) {
  return _.template(text, data, {
    escape: /{{-([\s\S]+?)}}/g,
    evaluate: /{{([\s\S]+?)}}/g,
    interpolate: /{{=([\s\S]+?)}}/g
  });
};

yeoman.generators.Base.prototype.underscoreEngine = function (text, data) {
  return _.template(text, data, {
    escape: /_-([\s\S]+?)_/g,
    evaluate: /_([\s\S]+?)_/g,
    interpolate: /_=([\s\S]+?)_/g
  });
};

// Time to extend!
util.inherits(PlayBase, yeoman.generators.Base);

PlayBase.prototype.print = function (msg, context) {
  if (_.isString(msg)) {
    yeoman.generators.Base.prototype.log.write(msg, context);
  } else {
    console.log(arguments);
  }
};

PlayBase.prototype.debug = function (msg, context) {
  if (this.logLevel === "debug") {
    this.print(msg, context);
  }
};

PlayBase.prototype.getConfigPath = function () {
  return path.join(this.paths.root, CONFIG_FILE);
};

PlayBase.prototype.readConfig = function () {
  return this.readFileAsJson(this.getConfigPath(), DEFAULT_CONFIG);
};

PlayBase.prototype.writeConfig = function () {
  this.writeFileFromJson(this.getConfigPath(), this.config);
};

// Handling conf files
function getConfRegex (key) {
  return new RegExp("^" + key + "=(.*)$", "m");
};

PlayBase.prototype.readConfAsString = function (env) {
  env = env || "application";
  return this.readFileAsString(path.join(this.paths.conf, env + ".conf"));
};

PlayBase.prototype.writeConfFromString = function (stringConf, env) {
  env = env || "application";
  return this.writeFileFromString(stringConf, path.join(this.paths.conf, env + ".conf"));
};

PlayBase.prototype.hasConf = function (key, env) {
  return getConfRegex(key).test(this.readConfAsString(env));
};

PlayBase.prototype.getAllConfs = function (subkey, env) {
  // TODO: wrong, do not work, correct it when time
  return getConfRegex("[^=]*" + subkey + "[^=]*").exec(this.readConfAsString(env));
};

PlayBase.prototype.getConf = function (key, env) {
  return getConfRegex(key).exec(this.readConfAsString(env))[1];
};

PlayBase.prototype.setConf = function (key, value, env) {
  if (this.hasConf(key, value)) {
    this.writeConfFromString(this.readConfAsString(env).replace( getConfRegex(key), key + "=" + value ), env);
  } else {
    this.writeConfFromString(this.readConfAsString(env) + "\n" + key + "=" + value, env);
  }
};

// Handling Build.scala and plugins.sbt files
PlayBase.prototype.readBuildAsString = function () {
  return this.readFileAsString(path.join(this.paths.project, "Build.scala"));
};

PlayBase.prototype.writeBuildFromString = function (stringBuild) {
  this.writeFileFromString(stringConf, path.join(this.paths.project, "Build.scala"));
};

PlayBase.prototype.readPluginsAsString = function () {
  return this.readFileAsString(path.join(this.paths.project, "plugins.sbt"));
};

PlayBase.prototype.writePluginsFromString = function (stringBuild) {
  this.writeFileFromString(stringConf, path.join(this.paths.project, "plugins.Sbt"));
};

PlayBase.prototype.getDependencyRegex = function (packageName, name, version) {
  name = name || "[a-zA-Z0-9_-]+";
  version = version || "[0-9]+(\.[0-9]+(\.[0-9]+(-SNAPSHOT)?)?)?";
  return new RegExp('"' + packageName + '" %% "' + name + '" % "' + version + '"');
};

PlayBase.prototype.hasDependency = function (packageName, name, version) {
  return this.getDependencyRegex(packageName, name, version).test( this.readBuildAsString() );
};

PlayBase.prototype.hasResolver = function (url) {
  return _.contains( this.readPluginsAsString(), url );
};

PlayBase.prototype.addResolver = function (url, description) {
  var newLine = 'resolvers += "' + (description ? description + '" at "' : '') + url + '"';
  this.writePluginsFromString( this.appendLine( this.readPluginsAsString(), newLine) );
};

PlayBase.prototype.removeResolver = function (url) {

};
<commit_msg>Add some ascii art<commit_after>'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs');
var _ = require('lodash');
var chalk = require('chalk');
var yeoman = require('yeoman-generator');

var CONFIG_FILE = "generator-play.json";

var DEFAULT_CONFIG = {
	global: {
    emptyBrackets: '()'
  },
  app: {
    name: '',
    version: '0.0.1-SNAPSHOT',
    playVersion: '2.1.3',
    language: '',
    secret: '',
    langs: [],
    dependencies: []
  }
};

var PATH_ROOT = process.cwd();

var PlayBase = module.exports =  function PlayBase(args, options) {
  this.logLevel = options["log-level"] || (options.verbose && "debug") || "info";

  yeoman.generators.Base.apply(this, arguments);

  // Set paths
  this.paths = {
    "root": PATH_ROOT,
    "app": path.join(PATH_ROOT, '/app'),
    "conf": path.join(PATH_ROOT, '/conf'),
    "public": path.join(PATH_ROOT, '/public'),
    "resources": path.join(PATH_ROOT, '/resources'),
    "project": path.join(PATH_ROOT, '/project'),
    "test": path.join(PATH_ROOT, '/test')
  };

  // Extend the Yeoman Generator Base
  this.pkg = this.readFileAsJson(path.join(this.paths.root, '/package.json'), {});
  this.config = this.readConfig();

  _.mixin({
    capitalize: function (string) {
      return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
    }
  });

  var playStyle = chalk.green;
  var generatorStyle = chalk.cyan;
  this.log.writeln(playStyle("       _             _   ") + generatorStyle("                                _             "));
  this.log.writeln(playStyle("      | |           | |  ") + generatorStyle("                               | |            "));
  this.log.writeln(playStyle(" _ __ | | __ _ _   _| |  ") + generatorStyle(" __ _  ___ _ __   ___ _ __ __ _| |_ ___  _ __ "));
  this.log.writeln(playStyle("| '_ \\| |/ _` | | | | | ") + generatorStyle(" / _` |/ _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\| '__|"));
  this.log.writeln(playStyle("| |_) | | (_| | |_| |_| ") + generatorStyle("| (_| |  __/ | | |  __/ | | (_| | || (_) | |   "));
  this.log.writeln(playStyle("| .__/|_|\\__,_|\\__, (_)") + generatorStyle("  \\__, |\\___|_| |_|\\___|_|  \\__,_|\\__\\___/|_|   "));
  this.log.writeln(playStyle("| |             __/ |    ") + generatorStyle(" __/ |                                        "));
  this.log.writeln(playStyle("|_|            |___/     ") + generatorStyle("|___/                                         "));

  this.on('end', function () {
    this.writeConfig();
  });

};

// Generic methods
yeoman.generators.Base.prototype.exists = function (val) {
	return !_.isNull(val) && !_.isUndefined(val);
};

yeoman.generators.Base.prototype.existsFile = function (path) {
	return fs.existsSync(path);
};

yeoman.generators.Base.prototype.readFileAsJson = function(path, defaultValue) {
	return this.existsFile(path) && JSON.parse(this.readFileAsString(path)) || defaultValue;
};

yeoman.generators.Base.prototype.writeFileFromJson = function(path, value, space) {
  space = space || "  ";
  return this.writeFileFromString(JSON.stringify(value, null, space), path);
};

yeoman.generators.Base.prototype.promptLoop = function (prompts, done) {
  var self = this;
  var results = [];
  var isEnded = false;
  var isMultiple = _.isArray(prompts);
  prompts = isMultiple ? prompts : [prompts];

  var cb = self.async();

  // Return true if the value should stop the loop
  // Possible values are null, undefined or empty string
  // Otherwise, return false
  function isQuitAnswer (promptValue) {
    return _.isUndefined(promptValue) || _.isNull(promptValue) || promptValue === "";
  };

  // Handle a prompt by updating the answer with its value
  // or end the loop
  function handlePrompt (prompt, props, answer) {
    var promptName = prompt.name;
    var promptValue = props[promptName];
    self.print(promptName, promptValue);

    if (isQuitAnswer(promptValue)) {
      self.debug(">> End prompt loop because of empty answer");
      isEnded = true;
    } else {
      answer[promptName] = promptValue;
    }
  };

  // Perform one iteration of the loop.
  // Depending on the answer(s), will call itself
  // to iterate one more step or stop the loop.
  function ask () {
    self.prompt(prompts, function (props, err) {
      if (err) {
        self.debug(">> End prompt loop because of error: ", err);
        isEnded = true;
        done(results, err);
      }

      var answer = {};

      _.map(prompts, function (prompt) {
        handlePrompt(prompt, props, answer);
      });

      if (!isEnded) {
        results.push(isMultiple ? answer : answer[prompts[0].name]);
        self.debug("Ask one more time. Current status: ", results);
        ask();
      } else {
        self.debug("End of prompt loop. Final status: ", results);
        done(results, null);
      }
    }.bind(self));
  };

  // Ask at least one time the prompts
  ask();
};

yeoman.generators.Base.prototype.appendLine = function (contentString, newLine) {
  return contentString + "\n" + newLine;
};

yeoman.generators.Base.prototype.mustacheEngine = function (text, data) {
  return _.template(text, data, {
    escape: /{{-([\s\S]+?)}}/g,
    evaluate: /{{([\s\S]+?)}}/g,
    interpolate: /{{=([\s\S]+?)}}/g
  });
};

yeoman.generators.Base.prototype.underscoreEngine = function (text, data) {
  return _.template(text, data, {
    escape: /_-([\s\S]+?)_/g,
    evaluate: /_([\s\S]+?)_/g,
    interpolate: /_=([\s\S]+?)_/g
  });
};

// Time to extend!
util.inherits(PlayBase, yeoman.generators.Base);

PlayBase.prototype.print = function (msg, context) {
  if (_.isString(msg)) {
    yeoman.generators.Base.prototype.log.write(msg, context);
  } else {
    console.log(arguments);
  }
};

PlayBase.prototype.debug = function (msg, context) {
  if (this.logLevel === "debug") {
    this.print(msg, context);
  }
};

PlayBase.prototype.getConfigPath = function () {
  return path.join(this.paths.root, CONFIG_FILE);
};

PlayBase.prototype.readConfig = function () {
  return this.readFileAsJson(this.getConfigPath(), DEFAULT_CONFIG);
};

PlayBase.prototype.writeConfig = function () {
  this.writeFileFromJson(this.getConfigPath(), this.config);
};

// Handling conf files
function getConfRegex (key) {
  return new RegExp("^" + key + "=(.*)$", "m");
};

PlayBase.prototype.readConfAsString = function (env) {
  env = env || "application";
  return this.readFileAsString(path.join(this.paths.conf, env + ".conf"));
};

PlayBase.prototype.writeConfFromString = function (stringConf, env) {
  env = env || "application";
  return this.writeFileFromString(stringConf, path.join(this.paths.conf, env + ".conf"));
};

PlayBase.prototype.hasConf = function (key, env) {
  return getConfRegex(key).test(this.readConfAsString(env));
};

PlayBase.prototype.getAllConfs = function (subkey, env) {
  // TODO: wrong, do not work, correct it when time
  return getConfRegex("[^=]*" + subkey + "[^=]*").exec(this.readConfAsString(env));
};

PlayBase.prototype.getConf = function (key, env) {
  return getConfRegex(key).exec(this.readConfAsString(env))[1];
};

PlayBase.prototype.setConf = function (key, value, env) {
  if (this.hasConf(key, value)) {
    this.writeConfFromString(this.readConfAsString(env).replace( getConfRegex(key), key + "=" + value ), env);
  } else {
    this.writeConfFromString(this.readConfAsString(env) + "\n" + key + "=" + value, env);
  }
};

// Handling Build.scala and plugins.sbt files
PlayBase.prototype.readBuildAsString = function () {
  return this.readFileAsString(path.join(this.paths.project, "Build.scala"));
};

PlayBase.prototype.writeBuildFromString = function (stringBuild) {
  this.writeFileFromString(stringConf, path.join(this.paths.project, "Build.scala"));
};

PlayBase.prototype.readPluginsAsString = function () {
  return this.readFileAsString(path.join(this.paths.project, "plugins.sbt"));
};

PlayBase.prototype.writePluginsFromString = function (stringBuild) {
  this.writeFileFromString(stringConf, path.join(this.paths.project, "plugins.Sbt"));
};

PlayBase.prototype.getDependencyRegex = function (packageName, name, version) {
  name = name || "[a-zA-Z0-9_-]+";
  version = version || "[0-9]+(\.[0-9]+(\.[0-9]+(-SNAPSHOT)?)?)?";
  return new RegExp('"' + packageName + '" %% "' + name + '" % "' + version + '"');
};

PlayBase.prototype.hasDependency = function (packageName, name, version) {
  return this.getDependencyRegex(packageName, name, version).test( this.readBuildAsString() );
};

PlayBase.prototype.hasResolver = function (url) {
  return _.contains( this.readPluginsAsString(), url );
};

PlayBase.prototype.addResolver = function (url, description) {
  var newLine = 'resolvers += "' + (description ? description + '" at "' : '') + url + '"';
  this.writePluginsFromString( this.appendLine( this.readPluginsAsString(), newLine) );
};

PlayBase.prototype.removeResolver = function (url) {

};
<|endoftext|>


<commit_before>'use strict';
/**
 * Controller for the admin page.
 */

const co = require('co');
const _ = require('lodash');
const api = require('../services/api-proxy');
const Promise = require('bluebird');
const logger = require('../services/logger');
const config = require('../config/config');
const axios = require('axios');

let admin = {};

const defaultOptions = (req) => {
  return {
    error: req.flash('error'),
    success: req.flash('success'),
    layout: 'master',
    isLoggedIn: req.user,
    language: req.language
  };
};

admin.showDashboard = (req, res) => {
  let options = defaultOptions(req);
  options.view = 'admin-dashboard';
  options.data = {};
  res.render('static/admin/dashboard', options);
};

admin.showDashboardEmails = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-emails';
  let emailAddress = req.query.emailAddress;

  if(emailAddress) {
    options.data = {};
    const emails = yield admin.getAllEmailsTo(emailAddress);

    // Date in JS is in ms but api response is in s
    options.data.emails = emails.map(email => {
      email.create_date = email.create_date * 1000;
      return email;
    });
  }

  res.render('static/admin/dashboard', options);
};

admin.showDashboardPayment = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-payment';
  options.data = yield admin.getInvoices(req);
  options.data = options.data
    .filter(invoice => invoice.event.isCurrent)
    .sort(invoice => invoice.event.id)
    .reverse();
  res.render('static/admin/dashboard', options);
};

admin.showDashboardCheckin = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-checkin';
  options.data = yield admin.getAllCurrentTeams(req);
  res.render('static/admin/dashboard', options);
};

admin.showOverview = function*(req, res) {

  if (!req.query.sortBy) {
    req.query.sortBy = 'lastContact';
    req.query.direction = 'up';
  }

  function compare(a,b) {
    if(a[req.query.sortBy]){
      if(req.query.direction === 'up'){
        if (a[req.query.sortBy].timestamp< b[req.query.sortBy].timestamp)
          return -1;
        if (a[req.query.sortBy].timestamp > b[req.query.sortBy].timestamp)
          return 1;
        return 0;
      }
      else if(req.query.direction === 'down'){
        if (a[req.query.sortBy].timestamp> b[req.query.sortBy].timestamp)
          return -1;
        if (a[req.query.sortBy].timestamp < b[req.query.sortBy].timestamp)
          return 1;
        return 0;
      }
    }
  }

  let options = defaultOptions(req);
  options.view = 'admin-teamoverview';                 // TODO: .then should be moved to api layer
  options.data = yield api.getTeamOverview(getAccessTokenFromRequest(req)).then(resp => resp.data);

  let callReasons = [
    'Technical Problem',
    '5h Update',
    'New Transport',
    'Finished BreakOut',
    'Sickness',
    'Emergency',
    'Other'
  ];

  options.data = options.data.map(function(team){
    team.lastContact = {};
    team.callReasons = callReasons;

    let validInfo = [team.lastContactWithHeadquarters, team.lastPosting, team.lastLocation].filter(function(elem){
      return elem;
    });

    let validTimestamps = validInfo.map(function(info){
      return info.timestamp;
    }).filter(function(elem){
      return elem;
    });

    if(validTimestamps.length != 0){
      team.lastContact.timestamp = Math.max.apply(Math, validTimestamps);
    } else {
      team.lastContact.timestamp = null;
    }

    if(!team.lastContactWithHeadquarters) {
      team.lastContactWithHeadquarters = {};
      team.lastContactWithHeadquarters.timestamp = null;
    }

    if(!team.lastPosting) {
      team.lastPosting = {};
      team.lastPosting.timestamp = null;
    }

    if(!team.lastLocation) {
      team.lastLocation = {};
      team.lastLocation.timestamp = null;
    }

    return team;
  });

  if(req.query.sortBy){
    options.data = options.data.sort(compare);
  }

  res.render('static/admin/dashboard', options);
};

function getAccessTokenFromRequest(req) {
  return req.user.access_token;
}

admin.showDashboardInvoice = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-invoice';
  if(req.query.eventId) {
    options.data = yield admin.getSponsoringInvoicesByEventId(req.user, req.query.eventId);
  }

  res.render('static/admin/dashboard', options);
};

admin.getSponsoringInvoicesByEventId = function(tokens, eventId) {
  return api.getModel(`/sponsoringinvoice/${eventId}/`, tokens);
};

admin.addPayment = function *(req, res) {
  logger.info(`Trying to add payment for invoice ${req.body.invoice}`);

  let body = {
    amount: req.body.amount,
    fidorId: req.body.fidorId,
    date: Math.round(new Date().getTime() / 1000)
  };

  try {
    yield api.postModel(`invoice/${req.body.invoice}/payment/`, req.user, body);
    res.sendStatus(200);
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to add a payment to invoice ${req.body.invoice}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }

  }
};

admin.setTeamSleepStatus = function *(req, res) {
  try {
    let team = yield api.putModel(`event/${req.body.eventid}/team`, req.body.teamid, req.user, { asleep: req.body.asleep });
    res.redirect('/admin/teamoverview/');
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to update the last contact ${req.body.update}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }
  }
};

admin.updateLastContact = function *(req, res) {

  try {
    let comment = yield api.postModel(`teamoverview/${req.body.teamid}/lastContactWithHeadquarters/`, req.user, { comment: req.body.update, reason: req.body.reason });
    res.redirect('/admin/teamoverview/');
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to update the last contact ${req.body.update}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }

  }
};

admin.getAllEmailsTo = function(emailAddress) {
  return axios.get(`https://mail.break-out.org/get/to/${emailAddress}`, {
    headers: {
      'X-AUTH-TOKEN': config.mailer.x_auth_token
    }
  }).then(resp => resp.data);
};

admin.getInvoices = (req) => co(function*() {
  const events = yield api.getModel('event', req.user);

  let teamsByEvent = yield events.map((e) => api.getModel(`event/${e.id}/team`, req.user));
  let allTeams = _.flatten(teamsByEvent);

  let allInvoices = [];

  for (let i = 0; i < allTeams.length; i++) {
    let t = allTeams[i];
    if (t.members.length > 1) {
      let invoice = yield api.getModel('invoice/teamfee', req.user, t.invoiceId);
      // TODO: This logic is very fragile b.c. it merges together two requests
      invoice.event = events.find(event => event.id === t.event);
      invoice.members = t.members;
      invoice.teamName = t.name;
      invoice.id = t.invoiceId;
      invoice.teamId = t.id;
      if (invoice.payments.length) {
        invoice.open = invoice.amount - invoice.payments.reduce((prev, curr) => {
          return prev + curr.amount;
        }, 0);
      } else {
        invoice.open = invoice.amount;
      }
      invoice.datesFidorIds = invoice.payments.map((payment) => `${new Date(payment.date * 1000).toLocaleDateString()} (id: ${payment.fidorId})`).join(', ');
      allInvoices.push(invoice);
    }
  }

  return allInvoices;
}).catch((ex) => {
  throw ex;
});

admin.getAllCurrentTeams = function () {
  return Promise.resolve(api.event.all())
    .filter(event => event.isCurrent)
    .map(event => api.team.getAllByEvent(event.id))
    .reduce((a, b) => a.concat(b), [])
    .filter(team => team.hasFullyPaid);
};

admin.checkinTeam = function *(req, res) {

  const tokens = req.user;
  const update = {hasStarted: true};

  let checkin = yield api.putModel(`event/${req.body.event}/team`, req.body.team, tokens, update);

  if (!checkin) return res.sendStatus(500);

  return res.sendStatus(200);
};

admin.getAllInvoices = (req) => co(function*() {

  let rawInvoices = yield api.invoice.getAll(req.user);

  var teams = [];

  var invoices = rawInvoices.map(i => {
    if (i.payments.length) {
      i.payed = i.payments.reduce((prev, curr) => {
        return prev + curr.amount;
      }, 0);
      i.open = i.amount - i.payed;
      if (i.open < 0) i.open = 0;
    } else {
      i.payed = 0;
      i.open = i.amount;
    }
    return i;
  });

  invoices.forEach(i => {
    if (!teams[i.teamId]) teams[i.teamId] = 0;
    teams[i.teamId] += i.payed;
  });

  // TODO: This is unused, what is the reason?
  var depositTeams = teams.map((t, index) => {
    if (t > 100) {
      return {
        teamId: index,
        amount: t
      };
    }
  });

  return invoices;

}).catch((ex) => {
  throw ex;
});

admin.addAmountToInvoice = function *(req, res) {

  let addAmount = yield api.invoice.addAmount(req.user, req.body.invoiceId, req.body.amount);

  if (!addAmount) return res.sendStatus(500);

  return res.sendStatus(200);
};

admin.addInvoice = function *(req, res) {

  var body = {
    firstname: req.body.firstname,
    lastname: req.body.lastname,
    company: req.body.company,
    teamId: parseFloat(req.body.teamId),
    amount: parseFloat(req.body.amount)
  };

  let addAmount = yield api.invoice.create(req.user, body);

  if (!addAmount) return res.sendStatus(500);

  return res.status(200).send(addAmount);

};

module.exports = admin;
<commit_msg>Adding performance improvements to team fee site<commit_after>'use strict';
/**
 * Controller for the admin page.
 */

const co = require('co');
const _ = require('lodash');
const api = require('../services/api-proxy');
const Promise = require('bluebird');
const logger = require('../services/logger');
const config = require('../config/config');
const axios = require('axios');

let admin = {};

const defaultOptions = (req) => {
  return {
    error: req.flash('error'),
    success: req.flash('success'),
    layout: 'master',
    isLoggedIn: req.user,
    language: req.language
  };
};

admin.showDashboard = (req, res) => {
  let options = defaultOptions(req);
  options.view = 'admin-dashboard';
  options.data = {};
  res.render('static/admin/dashboard', options);
};

admin.showDashboardEmails = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-emails';
  let emailAddress = req.query.emailAddress;

  if(emailAddress) {
    options.data = {};
    const emails = yield admin.getAllEmailsTo(emailAddress);

    // Date in JS is in ms but api response is in s
    options.data.emails = emails.map(email => {
      email.create_date = email.create_date * 1000;
      return email;
    });
  }

  res.render('static/admin/dashboard', options);
};

admin.showDashboardPayment = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-payment';
  options.data = yield admin.getInvoices(req);
  options.data = options.data
    .filter(invoice => invoice.event.isCurrent)
    .sort(invoice => invoice.event.id)
    .reverse();
  res.render('static/admin/dashboard', options);
};

admin.showDashboardCheckin = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-checkin';
  options.data = yield admin.getAllCurrentTeams(req);
  res.render('static/admin/dashboard', options);
};

admin.showOverview = function*(req, res) {

  if (!req.query.sortBy) {
    req.query.sortBy = 'lastContact';
    req.query.direction = 'up';
  }

  function compare(a,b) {
    if(a[req.query.sortBy]){
      if(req.query.direction === 'up'){
        if (a[req.query.sortBy].timestamp< b[req.query.sortBy].timestamp)
          return -1;
        if (a[req.query.sortBy].timestamp > b[req.query.sortBy].timestamp)
          return 1;
        return 0;
      }
      else if(req.query.direction === 'down'){
        if (a[req.query.sortBy].timestamp> b[req.query.sortBy].timestamp)
          return -1;
        if (a[req.query.sortBy].timestamp < b[req.query.sortBy].timestamp)
          return 1;
        return 0;
      }
    }
  }

  let options = defaultOptions(req);
  options.view = 'admin-teamoverview';                 // TODO: .then should be moved to api layer
  options.data = yield api.getTeamOverview(getAccessTokenFromRequest(req)).then(resp => resp.data);

  let callReasons = [
    'Technical Problem',
    '5h Update',
    'New Transport',
    'Finished BreakOut',
    'Sickness',
    'Emergency',
    'Other'
  ];

  options.data = options.data.map(function(team){
    team.lastContact = {};
    team.callReasons = callReasons;

    let validInfo = [team.lastContactWithHeadquarters, team.lastPosting, team.lastLocation].filter(function(elem){
      return elem;
    });

    let validTimestamps = validInfo.map(function(info){
      return info.timestamp;
    }).filter(function(elem){
      return elem;
    });

    if(validTimestamps.length != 0){
      team.lastContact.timestamp = Math.max.apply(Math, validTimestamps);
    } else {
      team.lastContact.timestamp = null;
    }

    if(!team.lastContactWithHeadquarters) {
      team.lastContactWithHeadquarters = {};
      team.lastContactWithHeadquarters.timestamp = null;
    }

    if(!team.lastPosting) {
      team.lastPosting = {};
      team.lastPosting.timestamp = null;
    }

    if(!team.lastLocation) {
      team.lastLocation = {};
      team.lastLocation.timestamp = null;
    }

    return team;
  });

  if(req.query.sortBy){
    options.data = options.data.sort(compare);
  }

  res.render('static/admin/dashboard', options);
};

function getAccessTokenFromRequest(req) {
  return req.user.access_token;
}

admin.showDashboardInvoice = function*(req, res) {
  let options = defaultOptions(req);
  options.view = 'admin-invoice';
  if(req.query.eventId) {
    options.data = yield admin.getSponsoringInvoicesByEventId(req.user, req.query.eventId);
  }

  res.render('static/admin/dashboard', options);
};

admin.getSponsoringInvoicesByEventId = function(tokens, eventId) {
  return api.getModel(`/sponsoringinvoice/${eventId}/`, tokens);
};

admin.addPayment = function *(req, res) {
  logger.info(`Trying to add payment for invoice ${req.body.invoice}`);

  let body = {
    amount: req.body.amount,
    fidorId: req.body.fidorId,
    date: Math.round(new Date().getTime() / 1000)
  };

  try {
    yield api.postModel(`invoice/${req.body.invoice}/payment/`, req.user, body);
    res.sendStatus(200);
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to add a payment to invoice ${req.body.invoice}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }

  }
};

admin.setTeamSleepStatus = function *(req, res) {
  try {
    let team = yield api.putModel(`event/${req.body.eventid}/team`, req.body.teamid, req.user, { asleep: req.body.asleep });
    res.redirect('/admin/teamoverview/');
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to update the last contact ${req.body.update}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }
  }
};

admin.updateLastContact = function *(req, res) {

  try {
    let comment = yield api.postModel(`teamoverview/${req.body.teamid}/lastContactWithHeadquarters/`, req.user, { comment: req.body.update, reason: req.body.reason });
    res.redirect('/admin/teamoverview/');
  } catch (err) {
    res.status(500);
    logger.error(`An error occured while trying to update the last contact ${req.body.update}: `, err);
    if (err.message) {
      res.json({message: err.message});
    } else {
      res.json({message: 'An unknown error occured'});
    }

  }
};

admin.getAllEmailsTo = function(emailAddress) {
  return axios.get(`https://mail.break-out.org/get/to/${emailAddress}`, {
    headers: {
      'X-AUTH-TOKEN': config.mailer.x_auth_token
    }
  }).then(resp => resp.data);
};

admin.getInvoices = (req) => co(function*() {
  const events = yield api.getModel('event', req.user);

  let teamsByEvent = yield events
    .filter((e) => e.isCurrent)
    .map((e) => api.getModel(`event/${e.id}/team/teamfee`, req.user));

  let allTeams = _.flatten(teamsByEvent);
  return allTeams.map((x) => {
    let payed = x.invoice.payments.reduce((prev, curr) => {
      return prev + curr.amount;
    }, 0);

    return {
      ...x.invoice,
      event: events.find(event => event.id === x.team.event),
      members: x.team.members,
      teamName: x.team.name,
      id: x.team.invoiceId,
      teamId: x.team.id,
      open: x.invoice.amount - payed,
      datesFidorIds: x.invoice.payments.map((payment) => `${new Date(payment.date * 1000).toLocaleDateString()} (id: ${payment.fidorId})`).join(', ')
    }
  });
}).catch((ex) => {
  throw ex;
});

admin.getAllCurrentTeams = function () {
  return Promise.resolve(api.event.all())
    .filter(event => event.isCurrent)
    .map(event => api.team.getAllByEvent(event.id))
    .reduce((a, b) => a.concat(b), [])
    .filter(team => team.hasFullyPaid);
};

admin.checkinTeam = function *(req, res) {

  const tokens = req.user;
  const update = {hasStarted: true};

  let checkin = yield api.putModel(`event/${req.body.event}/team`, req.body.team, tokens, update);

  if (!checkin) return res.sendStatus(500);

  return res.sendStatus(200);
};

admin.getAllInvoices = (req) => co(function*() {

  let rawInvoices = yield api.invoice.getAll(req.user);

  var teams = [];

  var invoices = rawInvoices.map(i => {
    if (i.payments.length) {
      i.payed = i.payments.reduce((prev, curr) => {
        return prev + curr.amount;
      }, 0);
      i.open = i.amount - i.payed;
      if (i.open < 0) i.open = 0;
    } else {
      i.payed = 0;
      i.open = i.amount;
    }
    return i;
  });

  invoices.forEach(i => {
    if (!teams[i.teamId]) teams[i.teamId] = 0;
    teams[i.teamId] += i.payed;
  });

  // TODO: This is unused, what is the reason?
  var depositTeams = teams.map((t, index) => {
    if (t > 100) {
      return {
        teamId: index,
        amount: t
      };
    }
  });

  return invoices;

}).catch((ex) => {
  throw ex;
});

admin.addAmountToInvoice = function *(req, res) {

  let addAmount = yield api.invoice.addAmount(req.user, req.body.invoiceId, req.body.amount);

  if (!addAmount) return res.sendStatus(500);

  return res.sendStatus(200);
};

admin.addInvoice = function *(req, res) {

  var body = {
    firstname: req.body.firstname,
    lastname: req.body.lastname,
    company: req.body.company,
    teamId: parseFloat(req.body.teamId),
    amount: parseFloat(req.body.amount)
  };

  let addAmount = yield api.invoice.create(req.user, body);

  if (!addAmount) return res.sendStatus(500);

  return res.status(200).send(addAmount);

};

module.exports = admin;
<|endoftext|>


<commit_before>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file;
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  var backupDir = path.join(dir, '.backup');
  fse.ensureDirSync(dir);
  fse.ensureDirSync(backupDir);

  self._backupDir = backupDir;
  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  self._backupProps = path.join(backupDir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files).forEach(function(file) {
    if (!/^(\d+)\.(.+)$/.test(file)) {
      return;
    }
    var index = parseInt(RegExp.$1, 10);
    var filename = RegExp.$2;
    try {
      filename = decodeURIComponent(filename);
    } catch(e) {
      logger.error(e);
    }
    if (index > maxIndex) {
      maxIndex = index;
    }
    files[filename] = {
      index: index,
      name: filename,
      data: readFileSafe(path.join(self._files, file)) || readFileSafe(path.join(backupDir, file))
    };
  });

  var properties = readJsonSafe(self._properties) || readJsonSafe(self._backupProps) || {};
  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: properties
  };

  var filesOrder = properties.filesOrder;
  if (!Array.isArray(filesOrder)) {
    filesOrder = null;
  }
  filesOrder = Object.keys(files).sort(function(cur, next) {
    if (filesOrder) {
      var curIndex = filesOrder.indexOf(cur);
      if (curIndex !== -1) {
        var nextIndex = filesOrder.indexOf(next);
        if (nextIndex !== -1) {
          return curIndex > nextIndex ? 1 : -1;
        }
      }
    }
    cur = files[cur];
    next = files[next];
    return cur.index > next.index ? 1 : -1;
  });
  this._cache.properties['filesOrder'] = filesOrder;
  fse.outputJsonSync(self._properties, self._cache.properties);
  fse.emptyDirSync(backupDir);
}

var proto = Storage.prototype;

proto._writeProperties = function() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (self._writePropertiesWaiting) {
      self._writePropertiesWaiting = false;
      self._writeProperties();
    } else if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._writeFile = function(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (file._waiting) {
      file._waiting = false;
      self._writeFile(file.name);
    } else if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._getFilePath = function(file) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  return file && path.join(this._files, file.index + '.' + name);
};

proto.count = function() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function(file) {
  return this._cache.files[file];
};

proto.getFileList = function(origObj) {
  var cache = this._cache;
  var files = cache.files;
  var filesOrder = cache.properties.filesOrder;
  return filesOrder.map(function(file) {
    return origObj ? files[file] : copyFileObj(files[file]);
  });
};

proto.writeFile = function(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  var hasChanged = true;
  data = typeof data == 'string' ? data : '';
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
    cache.properties.filesOrder.push(file);
    self._writeProperties();
  } else if (fileData.data === data) {
    hasChanged = false;
  }
  fileData.data = data;
  self._writeFile(file);
  return hasChanged;
};

proto.updateFile = function(file, data) {
  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder.splice(filesOrder.indexOf(file.name), 1);
  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  this._writeProperties();
  return true;
};

proto.renameFile = function(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file]) || cache.files[newFile]) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder[filesOrder.indexOf(file.name)] = newFile;
  var path = this._getFilePath(file);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), noop); //不考虑并发
  this._writeProperties();
  return true;
};

proto.moveTo = function(fromName, toName) {
  var filesOrder = this._cache.properties.filesOrder;
  var fromIndex = filesOrder.indexOf(fromName);
  if (fromIndex === -1) {
    return false;
  }
  var toIndex = filesOrder.indexOf(toName);
  if (toIndex === -1) {
    return false;
  }
  filesOrder.splice(fromIndex, 1);
  filesOrder.splice(toIndex, 0, fromName);
  this._writeProperties();
  return true;
};

proto.setProperty = function(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function(name) {
  return name in this._cache.properties;
};

proto.setProperties = function(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function(name) {
  if (this.hasProperty(name) && name !== 'filesOrder') {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<commit_msg>feat: Automatic backup file<commit_after>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file;
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  var backupDir = path.join(dir, '.backup');
  fse.ensureDirSync(dir);
  fse.ensureDirSync(backupDir);

  self._backupDir = backupDir;
  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  self._backupProps = path.join(backupDir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files).forEach(function(file) {
    if (!/^(\d+)\.(.+)$/.test(file)) {
      return;
    }
    var index = parseInt(RegExp.$1, 10);
    var filename = RegExp.$2;
    try {
      filename = decodeURIComponent(filename);
    } catch(e) {
      logger.error(e);
    }
    if (index > maxIndex) {
      maxIndex = index;
    }
    files[filename] = {
      index: index,
      name: filename,
      data: readFileSafe(path.join(self._files, file)) || readFileSafe(path.join(backupDir, file))
    };
  });

  var properties = readJsonSafe(self._properties) || readJsonSafe(self._backupProps) || {};
  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: properties
  };

  var filesOrder = properties.filesOrder;
  if (!Array.isArray(filesOrder)) {
    filesOrder = null;
  }
  filesOrder = Object.keys(files).sort(function(cur, next) {
    if (filesOrder) {
      var curIndex = filesOrder.indexOf(cur);
      if (curIndex !== -1) {
        var nextIndex = filesOrder.indexOf(next);
        if (nextIndex !== -1) {
          return curIndex > nextIndex ? 1 : -1;
        }
      }
    }
    cur = files[cur];
    next = files[next];
    return cur.index > next.index ? 1 : -1;
  });
  this._cache.properties['filesOrder'] = filesOrder;
  fse.outputJsonSync(self._properties, self._cache.properties);
  fse.emptyDirSync(backupDir);
}

var proto = Storage.prototype;

function copyFile(src, dest, callback) {
  if (typeof fs.copyFile === 'function') {
    fs.copyFile(src, dest, callback);
  } else {
    fse.copy(src, dest, callback);
  }
}

proto._writeProperties = function() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    } else {
      copyFile(self._properties, self._backupProps, function() {
        if (self._writePropertiesWaiting) {
          self._writePropertiesWaiting = false;
          self._writeProperties();
        }
      });
    }
  });
};

proto._writeFile = function(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    } else {
      copyFile(self._getFilePath(file), self._getFilePath(file, true), function() {
        if (file._waiting) {
          file._waiting = false;
          self._writeFile(file.name);
        }
      });
    }
  });
};

proto._getFilePath = function(file, backup) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  name = file.index + '.' + name;
  return path.join(backup ? this._backupDir : this._files, name);
};

proto.count = function() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function(file) {
  return this._cache.files[file];
};

proto.getFileList = function(origObj) {
  var cache = this._cache;
  var files = cache.files;
  var filesOrder = cache.properties.filesOrder;
  return filesOrder.map(function(file) {
    return origObj ? files[file] : copyFileObj(files[file]);
  });
};

proto.writeFile = function(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  var hasChanged = true;
  data = typeof data == 'string' ? data : '';
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
    cache.properties.filesOrder.push(file);
    self._writeProperties();
  } else if (fileData.data === data) {
    hasChanged = false;
  }
  fileData.data = data;
  self._writeFile(file);
  return hasChanged;
};

proto.updateFile = function(file, data) {
  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder.splice(filesOrder.indexOf(file.name), 1);
  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  fs.unlink(this._getFilePath(file, true), noop);
  this._writeProperties();
  return true;
};

proto.renameFile = function(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file]) || cache.files[newFile]) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder[filesOrder.indexOf(file.name)] = newFile;
  var path = this._getFilePath(file);
  var backupPath = this._getFilePath(file, true);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), function(err) {
    if (!err) {
      fs.unlink(backupPath, noop);
    }
  }); //不考虑并发
  this._writeProperties();
  return true;
};

proto.moveTo = function(fromName, toName) {
  var filesOrder = this._cache.properties.filesOrder;
  var fromIndex = filesOrder.indexOf(fromName);
  if (fromIndex === -1) {
    return false;
  }
  var toIndex = filesOrder.indexOf(toName);
  if (toIndex === -1) {
    return false;
  }
  filesOrder.splice(fromIndex, 1);
  filesOrder.splice(toIndex, 0, fromName);
  this._writeProperties();
  return true;
};

proto.setProperty = function(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function(name) {
  return name in this._cache.properties;
};

proto.setProperties = function(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function(name) {
  if (this.hasProperty(name) && name !== 'filesOrder') {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<|endoftext|>


<commit_before>/***
 * Useful definitions
 */
/*age.png              born.svg      Home_Icon.svg  other_family.png  school.svg        spouse.svg        UN_Members_Flags.JPG
age.svg              employee.svg  links          parents.png       shareholders.png  Tombstone.svg
alternate_names.svg  founder.svg   members.jpg    parent.svg        sibling.png       top_employee.jpg
*/

var _RELATIONS = [{
    "name": "per:age",
    "short": "age",
    "image": "age.svg",
    "icon" : "",
    "template": "{subject} is {object} old.",
    "subject-types": ["PER"],
    "object-types": ["NUM"]
  },{
    "name": "per:alternate_names",
    "short": "alias",
    "image": "alternate_names.svg",
    "icon": "",
    "template": "{subject} is also known as {object}.",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:place_of_birth",
    "short": "born at",
    "image": "born.svg",
    "icon": "",
    "template": "{subject} was born at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:place_of_residence",
    "short": "lived at",
    "image": "",
    "icon": "fa-home",
    "template": "{subject} lived at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:place_of_death",
    "short": "died at",
    "image": "Tombstone.svg",
    "icon": "",
    "template": "{subject} died at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:date_of_birth",
    "short": "born on",
    "image": "born.svg",
    "icon": "",
    "template": "{subject} was born on {object}.",
    "subject-types": ["PER"],
    "object-types": ["DATE"]
  },{
    "name": "per:date_of_death",
    "short": "died on",
    "template": "{subject} died on {object}.",
    "image": "Tombstone.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["DATE"]
  },{
    "name": "per:organizations_founded",
    "short": "founded",
    "template": "{subject} founded {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:holds_shares_in",
    "short": "holds shares in",
    "template": "{subject} holds shares in {object}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:schools_attended",
    "short": "studied at",
    "template": "{subject} studied at {object}.",
    "image": "school.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:employee_or_member_of",
    "short": "works for",
    "image": "employee.svg",
    "icon": "",
    "template": "{subject} works for {object}.",
    "subject-types": ["PER"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "per:parents",
    "short": "child of",
    "template": "{subject} is the child of {object}.",
    "image": "parents.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:children",
    "short": "parent of",
    "template": "{subject} is the parent of {object}.",
    "image": "parents.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:spouse",
    "short": "spouse",
    "template": "{subject} is the spouse of {object}.",
    "image": "spouse.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:sibling",
    "short": "sibling of",
    "template": "{subject} is the sibling of {object}.",
    "image": "sibling.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:other_family",
    "short": "other family",
    "template": "{subject} and {object} are otherwise family.",
    "image": "other_family.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:title",
    "short": "professional title",
    "image": "",
    "icon": "fa-id-card-o",
    "template": "{subject} is a {object}.",
    "subject-types": ["PER"],
    "object-types": ["TITLE"]
  },{
    "name": "org:alternate_names",
    "short": "alias",
    "image": "alternate_names.svg",
    "icon": "",
    "template": "{subject} is also known as {object}.",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:place_of_headquarters",
    "short": "headquartered at",
    "template": "{subject} is headquartered at {object}.",
    "image": "",
    "icon": "fa-building-o",
    "subject-types": ["ORG"],
    "object-types": ["GPE"]
  },{
    "name": "org:date_founded",
    "short": "founded on",
    "template": "{subject} was founded on {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["DATE"]
  },{
    "name": "org:date_dissolved",
    "short": "dissolved on",
    "template": "{subject} was closed/dissolved on {object}.",
    "image": "",
    "icon": "fa-trash-o",
    "subject-types": ["ORG"],
    "object-types": ["DATE"]
  },{
    "name": "org:founded_by",
    "short": "founded by",
    "template": "{subject} was founded by {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:member_of",
    "short": "member of",
    "template": "{subject} is a member of {object}.",
    "image": "members.jpg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:members",
    "short": "has member",
    "template": "{subject} has {object} as a member.",
    "image": "members.jpg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:subsidiaries",
    "short": "parent of",
    "template": "{subject} is a parent organization  of {object}.",
    "image": "",
    "icon": "fa-sitemap",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:parents",
    "short": "subsidiary of",
    "template": "{subject} is a subsidiary of {object}.",
    "image": "",
    "icon": "fa-sitemap",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:shareholders",
    "short": "shareholder",
    "template": "{object} is a shareholder of {subject}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:holds_shares_in",
    "short": "holds shares in",
    "template": "{subject} holds shares in {object}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "no_relation",
    "short": "unrelated",
    "template": "{subject} and {object} are otherwise related or not related.",
    "image" : "",
    "icon": "fa-times",
    "subject-types": ["PER", "ORG", "GPE"],
    "object-types": ["PER", "ORG", "GPE", "DATE", "NUM", "TITLE"]
  }];

var RelationLabel = function(r) {
  this.name = r.name;
  this.short = r.short;
  this.icon = r.icon;
  this.image = r.image;
  this.template = r.template;
  this.subjectTypes = r["subject-types"];
  this.objectTypes = r["object-types"];
}

RelationLabel.prototype.renderTemplate = function(mentionPair) {
  var subject = (mentionPair[0].entity) ? mentionPair[0].entity.gloss : mentionPair[0].gloss;
  var object = (mentionPair[1].entity) ? mentionPair[1].entity.gloss : mentionPair[1].gloss;
  return this.template
    .replace("{subject}", "" + subject + "")
    .replace("{object}", "" + object + "");
}

RelationLabel.prototype.isApplicable = function(mentionPair) {
  return this.subjectTypes.indexOf(mentionPair[0].type.name) >= 0
          && this.objectTypes.indexOf(mentionPair[1].type.name) >= 0;
}

var RELATIONS = [];
_RELATIONS.forEach(function(r) {RELATIONS.push(new RelationLabel(r))});

var _TYPES = [{
  "idx": 0,
  "name": "PER",
  "gloss": "Person",
  "icon": "fa-user",
  "linking": "wiki-search",
 },{
  "idx": 1,
  "name": "ORG",
  "gloss": "Organization",
  "icon": "fa-building",
  "linking": "wiki-search",
 },{
  "idx": 2,
  "name": "GPE",
  "gloss": "City/State/Country",
  "icon": "fa-globe",
  "linking": "wiki-search",
 },{
  "idx": 3,
  "name": "DATE",
  "gloss": "Date",
  "icon": "fa-calendar",
  "linking": "date-picker",
 },{
  "idx": 4,
  "name": "TITLE",
  "gloss": "Title",
  "icon": "fa-id-card-o",
  "linking": "",
 }];
var EntityType = function(t) {
  this.name = t.name;
  this.gloss = t.gloss;
  this.icon = t.icon;
}
var TYPES = {};
_TYPES.forEach(function(t) {TYPES[t.name] = t});

//
var Mention = function(m) {
  this.id = Mention.count++;
  this.tokens = m.tokens;
  this.sentenceIdx = m.tokens[0].sentenceIdx;
  this.type = m.type && TYPES[m.type];
  this.gloss = m.gloss;
  this.doc_char_begin = m.doc_char_begin;
  this.doc_char_end = m.doc_char_end;

  this.entity = m.entity;
}
Mention.count = 0;
Mention.fromTokens = function(tokens) {
  return new Mention({
    "tokens": tokens,
    "sentenceIdx": tokens[0].token.sentenceIdx,
    "type": undefined,
    "gloss": Mention.textFromTokens(tokens),
    "doc_char_begin": tokens[0].token.doc_char_begin,
    "doc_char_end": tokens[tokens.length-1].token.doc_char_end,
  });
}
Mention.textFromTokens = function(tokens) {
  text = "";
  for (i = 0; i < tokens.length; i++){
    text += tokens[i].textContent;
  }
  console.log(text);
  return text.replace(" ", " ");
}

// Returns the text represented within the spans of text.
Mention.prototype.text = function(){
  return Mention.textFromTokens(this.tokens);
}

// Compute levenshtein distance from input @string
Mention.prototype.levenshtein = function(string){
  return window.Levenshtein.get(this.text(), string);
}

// Creates a new entity from a @canonical_mention and @type.
function Entity(canonicalMention) {
  console.log(canonicalMention);
  this.idx = 1 + Entity.count++;
  this.id = "e-" + this.idx;
  this.gloss = canonicalMention.gloss;
  this.type = canonicalMention.type;
  this.mentions = [];

  this.addMention(canonicalMention);
}
Entity.count = 0;
Entity.map = {};

Entity.prototype.addMention = function(mention) {
  console.log("Adding mention", mention, this);
  // Set the type of the mention if it hasn't already been set.
  if (mention.type === null) {
    mention.type = this.type;
  }
  mention.entity = this;

  this.mentions = [mention];
  Entity.map[mention.id] = this;
}

// Returns "true" if the mention has > 0 mentions.
Entity.prototype.removeMention = function(mention) {
  console.log(mention, this.mentions);
  var index = this.mentions.indexOf(mention);
  console.assert(index > -1);
  if (index > -1) {
    this.mentions.splice(index, 1);
  }
  delete Entity.map[mention.id];

  return (this.mentions.length > 0);
};

// returns the minimum levenshtein distance from all the mentions in the
// entity.
Entity.prototype.levenshtein = function(mentionText) {
  var bestMatch = null
  var bestScore = 1000;
  for (var i = 0; i < this.mentions.length; i++) {
    var score = this.mentions[i].levenshtein(mentionText);
    if (bestScore > score) {
      bestScore = score;
      bestMatch = this.mentions[i]
    }
  }
  return bestScore;
}
<commit_msg>Fixed relation types and mention display.<commit_after>/***
 * Useful definitions
 */
/*age.png              born.svg      Home_Icon.svg  other_family.png  school.svg        spouse.svg        UN_Members_Flags.JPG
age.svg              employee.svg  links          parents.png       shareholders.png  Tombstone.svg
alternate_names.svg  founder.svg   members.jpg    parent.svg        sibling.png       top_employee.jpg
*/

var _RELATIONS = [{
    "name": "per:age",
    "short": "age",
    "image": "age.svg",
    "icon" : "",
    "template": "{subject} is {object} old.",
    "subject-types": ["PER"],
    "object-types": ["NUM"]
  },{
    "name": "per:alternate_names",
    "short": "alias",
    "image": "alternate_names.svg",
    "icon": "",
    "template": "{subject} is also known as {object}.",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:place_of_birth",
    "short": "born at",
    "image": "born.svg",
    "icon": "",
    "template": "{subject} was born at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:place_of_residence",
    "short": "lived at",
    "image": "",
    "icon": "fa-home",
    "template": "{subject} lived at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:place_of_death",
    "short": "died at",
    "image": "Tombstone.svg",
    "icon": "",
    "template": "{subject} died at {object}.",
    "subject-types": ["PER"],
    "object-types": ["GPE"]
  },{
    "name": "per:date_of_birth",
    "short": "born on",
    "image": "born.svg",
    "icon": "",
    "template": "{subject} was born on {object}.",
    "subject-types": ["PER"],
    "object-types": ["DATE"]
  },{
    "name": "per:date_of_death",
    "short": "died on",
    "template": "{subject} died on {object}.",
    "image": "Tombstone.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["DATE"]
  },{
    "name": "per:organizations_founded",
    "short": "founded",
    "template": "{subject} founded {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:holds_shares_in",
    "short": "holds shares in",
    "template": "{subject} holds shares in {object}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:schools_attended",
    "short": "studied at",
    "template": "{subject} studied at {object}.",
    "image": "school.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["ORG"]
  },{
    "name": "per:employee_or_member_of",
    "short": "works for",
    "image": "employee.svg",
    "icon": "",
    "template": "{subject} works for {object}.",
    "subject-types": ["PER"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "per:parents",
    "short": "child of",
    "template": "{subject} is the child of {object}.",
    "image": "parents.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:children",
    "short": "parent of",
    "template": "{subject} is the parent of {object}.",
    "image": "parents.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:spouse",
    "short": "spouse",
    "template": "{subject} is the spouse of {object}.",
    "image": "spouse.svg",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:sibling",
    "short": "sibling of",
    "template": "{subject} is the sibling of {object}.",
    "image": "sibling.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:other_family",
    "short": "other family",
    "template": "{subject} and {object} are otherwise family.",
    "image": "other_family.png",
    "icon": "",
    "subject-types": ["PER"],
    "object-types": ["PER"]
  },{
    "name": "per:title",
    "short": "professional title",
    "image": "",
    "icon": "fa-id-card-o",
    "template": "{subject} is a {object}.",
    "subject-types": ["PER"],
    "object-types": ["TITLE"]
  },{
    "name": "org:alternate_names",
    "short": "alias",
    "image": "alternate_names.svg",
    "icon": "",
    "template": "{subject} is also known as {object}.",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:place_of_headquarters",
    "short": "headquartered at",
    "template": "{subject} is headquartered at {object}.",
    "image": "",
    "icon": "fa-building-o",
    "subject-types": ["ORG"],
    "object-types": ["GPE"]
  },{
    "name": "org:date_founded",
    "short": "founded on",
    "template": "{subject} was founded on {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["DATE"]
  },{
    "name": "org:date_dissolved",
    "short": "dissolved on",
    "template": "{subject} was closed/dissolved on {object}.",
    "image": "",
    "icon": "fa-trash-o",
    "subject-types": ["ORG"],
    "object-types": ["DATE"]
  },{
    "name": "org:founded_by",
    "short": "founded by",
    "template": "{subject} was founded by {object}.",
    "image": "founder.svg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:member_of",
    "short": "member of",
    "template": "{subject} is a member of {object}.",
    "image": "members.jpg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:members",
    "short": "has member",
    "template": "{subject} has {object} as a member.",
    "image": "members.jpg",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:subsidiaries",
    "short": "parent of",
    "template": "{subject} is a parent organization  of {object}.",
    "image": "",
    "icon": "fa-sitemap",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:parents",
    "short": "subsidiary of",
    "template": "{subject} is a subsidiary of {object}.",
    "image": "",
    "icon": "fa-sitemap",
    "subject-types": ["ORG"],
    "object-types": ["ORG", "GPE"]
  },{
    "name": "org:shareholders",
    "short": "shareholder",
    "template": "{object} is a shareholder of {subject}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "org:holds_shares_in",
    "short": "holds shares in",
    "template": "{subject} holds shares in {object}.",
    "image": "shareholders.png",
    "icon": "",
    "subject-types": ["ORG"],
    "object-types": ["ORG"]
  },{
    "name": "no_relation",
    "short": "unrelated",
    "template": "{subject} and {object} are otherwise related or not related.",
    "image" : "",
    "icon": "fa-times",
    "subject-types": ["PER", "ORG", "GPE"],
    "object-types": ["PER", "ORG", "GPE", "DATE", "NUM", "TITLE"]
  }];

var RelationLabel = function(r) {
  this.name = r.name;
  this.short = r.short;
  this.icon = r.icon;
  this.image = r.image;
  this.template = r.template;
  this.subjectTypes = r["subject-types"];
  this.objectTypes = r["object-types"];
}

RelationLabel.prototype.renderTemplate = function(mentionPair) {
  var subject = (mentionPair[0].entity.gloss) ? mentionPair[0].entity.gloss : mentionPair[0].gloss;
  var object = (mentionPair[1].entity.gloss) ? mentionPair[1].entity.gloss : mentionPair[1].gloss;
  return this.template
    .replace("{subject}", "" + subject + "")
    .replace("{object}", "" + object + "");
}

RelationLabel.prototype.isApplicable = function(mentionPair) {
  return this.subjectTypes.indexOf(mentionPair[0].type.name) >= 0
          && this.objectTypes.indexOf(mentionPair[1].type.name) >= 0;
}

var RELATIONS = [];
_RELATIONS.forEach(function(r) {RELATIONS.push(new RelationLabel(r))});

var _TYPES = [{
  "idx": 0,
  "name": "PER",
  "gloss": "Person",
  "icon": "fa-user",
  "linking": "wiki-search",
 },{
  "idx": 1,
  "name": "ORG",
  "gloss": "Organization",
  "icon": "fa-building",
  "linking": "wiki-search",
 },{
  "idx": 2,
  "name": "GPE",
  "gloss": "City/State/Country",
  "icon": "fa-globe",
  "linking": "wiki-search",
 },{
  "idx": 3,
  "name": "DATE",
  "gloss": "Date",
  "icon": "fa-calendar",
  "linking": "date-picker",
 },{
  "idx": 4,
  "name": "TITLE",
  "gloss": "Title",
  "icon": "fa-id-card-o",
  "linking": "",
 }];
var EntityType = function(t) {
  this.name = t.name;
  this.gloss = t.gloss;
  this.icon = t.icon;
}
var TYPES = {};
_TYPES.forEach(function(t) {TYPES[t.name] = t});

//
var Mention = function(m) {
  this.id = Mention.count++;
  this.tokens = m.tokens;
  this.sentenceIdx = m.tokens[0].sentenceIdx;
  this.type = m.type && TYPES[m.type];
  this.gloss = m.gloss;
  this.doc_char_begin = m.doc_char_begin;
  this.doc_char_end = m.doc_char_end;

  this.entity = m.entity;
}
Mention.count = 0;
Mention.fromTokens = function(tokens) {
  return new Mention({
    "tokens": tokens,
    "sentenceIdx": tokens[0].token.sentenceIdx,
    "type": undefined,
    "gloss": Mention.textFromTokens(tokens),
    "doc_char_begin": tokens[0].token.doc_char_begin,
    "doc_char_end": tokens[tokens.length-1].token.doc_char_end,
  });
}
Mention.textFromTokens = function(tokens) {
  text = "";
  for (i = 0; i < tokens.length; i++){
    text += tokens[i].textContent;
  }
  console.log(text);
  return text.replace(" ", " ");
}

// Returns the text represented within the spans of text.
Mention.prototype.text = function(){
  return Mention.textFromTokens(this.tokens);
}

// Compute levenshtein distance from input @string
Mention.prototype.levenshtein = function(string){
  return window.Levenshtein.get(this.text(), string);
}

// Creates a new entity from a @canonical_mention and @type.
function Entity(canonicalMention) {
  console.log(canonicalMention);
  this.idx = 1 + Entity.count++;
  this.id = "e-" + this.idx;
  this.gloss = canonicalMention.gloss;
  this.type = canonicalMention.type;
  this.mentions = [];

  this.addMention(canonicalMention);
}
Entity.count = 0;
Entity.map = {};

Entity.prototype.addMention = function(mention) {
  console.log("Adding mention", mention, this);
  // Set the type of the mention if it hasn't already been set.
  if (mention.type === null) {
    mention.type = this.type;
  }
  mention.entity = this;

  this.mentions = [mention];
  Entity.map[mention.id] = this;
}

// Returns "true" if the mention has > 0 mentions.
Entity.prototype.removeMention = function(mention) {
  console.log(mention, this.mentions);
  var index = this.mentions.indexOf(mention);
  console.assert(index > -1);
  if (index > -1) {
    this.mentions.splice(index, 1);
  }
  delete Entity.map[mention.id];

  return (this.mentions.length > 0);
};

// returns the minimum levenshtein distance from all the mentions in the
// entity.
Entity.prototype.levenshtein = function(mentionText) {
  var bestMatch = null
  var bestScore = 1000;
  for (var i = 0; i < this.mentions.length; i++) {
    var score = this.mentions[i].levenshtein(mentionText);
    if (bestScore > score) {
      bestScore = score;
      bestMatch = this.mentions[i]
    }
  }
  return bestScore;
}
<|endoftext|>