<commit_before>const Ractive = require('ractive');
const fade = require('ractive-transitions-fade');
const { EVENT, bookmarks, github, helper } = require('../services');
const $ = require('../util');

const BuildStatus = require('./build-status');

const DEFAULT_REPO_NAME = 'Pages';				// for ungrouped pages
const DEFAULT_PROJECTS_REPO_NAME = 'Projects';	// for ungrouped projects
const issueTypeCls = {
	pr: 'ion-ios-git-pull-request',
	issue: 'ion-ios-bug-outline',
	project: 'ion-ios-cube-outline',
	page: 'ion-ios-document-outline',
	default: 'ion-ios-document-outline',
};

const template = `
	{{#groupedBookmarks:repo}}

				{{#if hasUrl }}
					{{repoShortName}}
				{{else}}
					{{repoShortName}}
				{{/if}}

				{{#items}}

						{{name}}
						{{#if type === 'pr'}}<BuildStatus issue="{{this}}" />{{/if}}

				{{/items}}

	{{/groupedBookmarks}}
`;

const data = {
	bookmarks: [],
	issueIcon: iss => issueTypeCls[iss.type],
	issueCls: iss => {
		const repo = (iss.repo || '').replace(/[\/\.]/g, '-').toLowerCase();
		return iss.id ? `issue-${repo}-${iss.id}` : '';
	},
};

let throttled = null;
const throttle = () => {
	if (throttled) clearTimeout(throttled);
	throttled = setTimeout(() => { throttled = null; }, 1000);
};

function openIssue (e) {
	e.original.preventDefault();
	if (throttled) return throttle();	// if clicked during quiet time - throttle again
	throttle();
	const iss = e.get();
	if (iss) {
		iss.unread = false;
		bookmarks.setUnreadByUrl(iss.url, false);
		$.trigger(EVENT.url.change.to, iss.url);
	}
}

function openRepo (e) {
	$.trigger(EVENT.url.change.to, e.get().repoUrl);
	return false;
}

function addBookmark (issue) {
	issue = copleteIssueModel(issue);
	bookmarks.add(issue);
	data.bookmarks.push(issue);
	render(data.bookmarks);
	github.checkIssuesForUpdates([issue]).then(() => render(data.bookmarks));
}

function removeBookmark (issue) {
	bookmarks.remove(issue);
	const idx = data.bookmarks.indexOf(issue);
	Module.splice('bookmarks', idx - 1, 1);
}

function onUrlChanged (wv, issue) {
	if (!issue || !issue.url) return;
	const iss = data.bookmarks.filter(i => i.url === issue.url)[0];
	if (iss) iss.unread = false;
	bookmarks.setUnreadByUrl(issue.url, false);
}

function refresh (full) {
	if (Module && full === true) {
		data.bookmarks = [];
		Module.reset(data);
	}

	bookmarks.get()
		.then(render)
		.then(github.checkIssuesForUpdates)
		.then(render);
}

function copleteIssueModel (iss) {
	if (!iss.repo) {
		if (helper.getPageActualTypeFromUrl(iss.url) === 'project') {
			iss.repo = DEFAULT_PROJECTS_REPO_NAME;
			iss.type = 'project';
		}
		else {
			iss.repo = DEFAULT_REPO_NAME;
			iss.type = 'page';
		}
	}
	iss.build = iss.build || {};
	return iss;
}

function render (issues) {
	issues = issues.map(copleteIssueModel);
	issues = helper.mergeArrays(issues, data.bookmarks);
	Module.set('bookmarks', issues);
	return issues;
}

function oninit () {
	$.on(EVENT.bookmark.add, addBookmark);
	$.on(EVENT.bookmark.remove, removeBookmark);
	$.on(EVENT.bookmarks.refresh, () => refresh(true));
	$.on(EVENT.url.change.done, onUrlChanged);
	this.on({ openRepo, openIssue });
	refresh();
}

const Module = new Ractive({
	el: '#subnav .subnav-bookmarks .subnav-section-list',
	data,
	template,
	oninit,
	components: { BuildStatus },
	computed: {
		groupedBookmarks: function () {
			return helper.groupIssues(this.get('bookmarks'));
		}
	},
	transitions: { fade }
});

module.exports = Module;
<commit_msg>remove bookmark correctly<commit_after>const Ractive = require('ractive');
const fade = require('ractive-transitions-fade');
const { EVENT, bookmarks, github, helper } = require('../services');
const $ = require('../util');

const BuildStatus = require('./build-status');

const DEFAULT_REPO_NAME = 'Pages';				// for ungrouped pages
const DEFAULT_PROJECTS_REPO_NAME = 'Projects';	// for ungrouped projects
const issueTypeCls = {
	pr: 'ion-ios-git-pull-request',
	issue: 'ion-ios-bug-outline',
	project: 'ion-ios-cube-outline',
	page: 'ion-ios-document-outline',
	default: 'ion-ios-document-outline',
};

const template = `
	{{#groupedBookmarks:repo}}

				{{#if hasUrl }}
					{{repoShortName}}
				{{else}}
					{{repoShortName}}
				{{/if}}

				{{#items}}

						{{name}}
						{{#if type === 'pr'}}<BuildStatus issue="{{this}}" />{{/if}}

				{{/items}}

	{{/groupedBookmarks}}
`;

const data = {
	bookmarks: [],
	issueIcon: iss => issueTypeCls[iss.type],
	issueCls: iss => {
		const repo = (iss.repo || '').replace(/[\/\.]/g, '-').toLowerCase();
		return iss.id ? `issue-${repo}-${iss.id}` : '';
	},
};

let throttled = null;
const throttle = () => {
	if (throttled) clearTimeout(throttled);
	throttled = setTimeout(() => { throttled = null; }, 1000);
};

function openIssue (e) {
	e.original.preventDefault();
	if (throttled) return throttle();	// if clicked during quiet time - throttle again
	throttle();
	const iss = e.get();
	if (iss) {
		iss.unread = false;
		bookmarks.setUnreadByUrl(iss.url, false);
		$.trigger(EVENT.url.change.to, iss.url);
	}
}

function openRepo (e) {
	$.trigger(EVENT.url.change.to, e.get().repoUrl);
	return false;
}

function addBookmark (issue) {
	issue = copleteIssueModel(issue);
	bookmarks.add(issue);
	data.bookmarks.push(issue);
	render(data.bookmarks);
	github.checkIssuesForUpdates([issue]).then(() => render(data.bookmarks));
}

function removeBookmark (issue) {
	bookmarks.remove(issue);
	Module.set('bookmarks', Module.get('bookmarks').filter(i => i.id !== issue.id));
}

function onUrlChanged (wv, issue) {
	if (!issue || !issue.url) return;
	const iss = data.bookmarks.filter(i => i.url === issue.url)[0];
	if (iss) iss.unread = false;
	bookmarks.setUnreadByUrl(issue.url, false);
}

function refresh (full) {
	if (Module && full === true) {
		data.bookmarks = [];
		Module.reset(data);
	}

	bookmarks.get()
		.then(render)
		.then(github.checkIssuesForUpdates)
		.then(render);
}

function copleteIssueModel (iss) {
	if (!iss.repo) {
		if (helper.getPageActualTypeFromUrl(iss.url) === 'project') {
			iss.repo = DEFAULT_PROJECTS_REPO_NAME;
			iss.type = 'project';
		}
		else {
			iss.repo = DEFAULT_REPO_NAME;
			iss.type = 'page';
		}
	}
	iss.build = iss.build || {};
	return iss;
}

function render (issues) {
	issues = issues.map(copleteIssueModel);
	issues = helper.mergeArrays(issues, data.bookmarks);
	Module.set('bookmarks', issues);
	return issues;
}

function oninit () {
	$.on(EVENT.bookmark.add, addBookmark);
	$.on(EVENT.bookmark.remove, removeBookmark);
	$.on(EVENT.bookmarks.refresh, () => refresh(true));
	$.on(EVENT.url.change.done, onUrlChanged);
	this.on({ openRepo, openIssue });
	refresh();
}

const Module = new Ractive({
	el: '#subnav .subnav-bookmarks .subnav-section-list',
	data,
	template,
	oninit,
	components: { BuildStatus },
	computed: {
		groupedBookmarks: function () {
			return helper.groupIssues(this.get('bookmarks'));
		}
	},
	transitions: { fade }
});

module.exports = Module;
<|endoftext|>


<commit_before>'use-strict';
var MongoClient = require('mongodb').MongoClient;
var fs = require('fs');
var assert = require('assert');
var argv = require('minimist')(process.argv.slice(2));

MongoClient.connect('mongodb://127.0.0.1:27017/github-downloads', function(err, db) {
    assert.equal(null, err);

    db.collection('downloads').find({}, function(err, result) {
        assert.equal(null, err);
        // assert.ok(result.length);

        console.log(typeof result);
        console.log(result);

        db.close();
    });
});
<commit_msg>modifications to mongo results<commit_after>'use-strict';
var MongoClient = require('mongodb').MongoClient;
var fs = require('fs');
var assert = require('assert');
var argv = require('minimist')(process.argv.slice(2));

MongoClient.connect('mongodb://127.0.0.1:27017/github-downloads', function(err, db) {
    assert.equal(null, err);

    db.collection('downloads').find({}).toArray(function(err, result) {
        assert.equal(null, err);
        // assert.ok(result.length);

        console.log(typeof result);
        console.log(JSON.stringify(result));

        db.close();
    });
});
<|endoftext|>


<commit_before>/*
	ZetaPushCore v1.0
	Javascript core sdk for ZetaPush
	Mikael Morvan - Mars 2015
	*/

;(function () {
	'use strict';

	/**
	 * Class for managing core functionnalities.
	 *
	 * @class ZetaPush Manages core functionnalities
	 */
	function ZetaPush() {
	}

	// Singleton for ZetaPush core
	var zp= new ZetaPush();
	var proto = ZetaPush.prototype;
	var exports = this;
	var originalGlobalValue = exports.ZetaPush;

	var cometd = $.cometd,
	_connectionData= null,
	connected = false,
	clientId,
	subscriptions = [];

	/*
		Listeners for cometD meta channels
	*/
	cometd.addListener('/meta/connect', function(msg) {
		if (cometd.isDisconnected()) {
			connected = false;
			log.info('connection closed');
			return;
		}

		var wasConnected = connected;
		connected = msg.successful;
		if (!wasConnected && connected) { // reconnected
			log.info('connection established');
			cometd.notifyListeners('/meta/connected', msg);
			cometd.batch(function(){
				zp.refresh();
			});
		} else if (wasConnected && !connected) {
			log.warn('connection broken');
		}
	});

	cometd.addListener('/meta/handshake', function(handshake) {
		if (handshake.successful) {
			log.debug('successful handshake', handshake);
			clientId = handshake.clientId;
		}
		else {
			log.warn('unsuccessful handshake');
			clientId = null;
		}
	});

	proto.isConnected= function(){
		return !cometd.isDisconnected();
	}
	/*
		Generate a channel
	*/
	proto.generateChannel= function(businessId, deploymentId, verb){
		return '/service/' + businessId +'/'+ deploymentId +'/'+ verb;
	}

	/*
		Generate a channel
	*/
	proto.generateMetaChannel= function(businessId, deploymentId, verb){
		return '/meta/' + businessId +'/'+ deploymentId +'/'+ verb;
	}

	/*
		Listener for every ZetaPush and CometD events

		Args:
		1 argument: a previous key (for refresh)
		2 arguments: a topic and a callback
		4 arguments: businessId, deploymentId, verb and callback
	*/
	proto.on= function(businessId, deploymentId, verb, callback){
		// One can call the function with a key
		if (arguments.length== 1){
			var key= arguments[0];
		}
		else if (arguments.length == 2){
			var key={};
			key.channel= arguments[0];
			key.callback= arguments[1];
			subscriptions.push(key);
		} else if (arguments.length == 4) {
			var key={};
			key.channel= proto.generateChannel(businessId, deploymentId, verb);
			key.callback= callback;
			subscriptions.push(key);
		} else{
			throw "zetaPush.on - bad arguments";
		}

		var tokens= key.channel.split("/");
		if (tokens.length<=1){
			cometd.notifyListeners('/meta/error', "Syntax error in the channel name");
			return null;
		}

		if (tokens[1]=='service'){
			key.isService= true;

			if (connected) {
				key.sub = cometd.subscribe(key.channel, key.callback);
				log.debug('subscribed', key);
			} else {
				log.debug('queuing subscription request', key);
			}

		} else if (tokens[1]=='meta'){
			key.isService= false;
			key.sub= cometd.addListener(key.channel, key.callback);
		} else {
			log.error("This event can t be managed by ZetaPush", evt);
			return null;
		}
		if (key.renewOnReconnect==null)
			key.renewOnReconnect = true;

		return key;
	}

	/*
		Remove listener
	*/
	proto.off= function(key){
		if (!key || key.sub==null)
			return;

		if (key.isService){
			cometd.unsubscribe(key.sub);
			key.sub= null;
		} else {
			cometd.removeListener(key.sub);
			key.sub= null;
		}
		log.debug('unsubscribed', key);
		key.renewOnReconnect = false;
	}

	/*
		Send data
		3 params
		businessId, deploymentId, verb (no data)
		4 params
		businessId, deploymentId, verb, data
		2 params
		channel, data
	*/
	proto.send= function(businessId, deploymentId, verb, data){

		var evt, sendData;

		if ((arguments.length== 2) || (arguments.length==1)){
			evt= arguments[0];
			sendData= arguments[1];
		}
		else if ((arguments.length==3) || (arguments.length==4)){
			evt= proto.generateChannel(businessId, deploymentId, verb);
			sendData= data;
		}

		var tokens= evt.split("/");
		if (tokens.length<=1){
			cometd.notifyListeners('/meta/error', "Syntax error in the channel name");
			return;
		}

		if (tokens[1]=='service'){
			if (connected){
				cometd.publish(evt, sendData);
			}
		}
		else if (tokens[1]=='meta'){
			cometd.notifyListeners(evt, sendData);
		}
	}

	/*
		Init ZetaPush with the server url
	*/
	proto.init= function(serverUrl, debugLevel){
		log.setLevel(debugLevel);
		if (debugLevel == 'debug')
			cometd.websocketEnabled= false;

		cometd.configure({
			url: serverUrl,
			logLevel: debugLevel,
			backoffIncrement: 100,
			maxBackoff: 500,
			appendMessageTypeToURL: false
		});
	}
	/*
		Disconnect ZetaPush
	*/
	proto.disconnect= function() {
		cometd.disconnect(true);
	}

	// http://cometd.org/documentation/cometd-javascript/subscription
	cometd.onListenerException = function(exception, subscriptionHandle, isListener, message) {
		log.error('Uncaught exception for subscription', subscriptionHandle, ':', exception, 'message:', message);
		if (isListener) {
			cometd.removeListener(subscriptionHandle);
			log.error('removed listener');
		} else {
			cometd.unsubscribe(subscriptionHandle);
			log.error('unsubscribed');
		}
		disconnect();
	};

	/*
		Refresh subscriptions
	*/
	proto.refresh= function() {
		log.debug('refreshing subscriptions');
		var renew = [];
		subscriptions.forEach(function(key) {
			if (key.sub){
				if (key.isService)
					cometd.unsubscribe(key.sub)
				else
					cometd.removeListener(key.sub);
			}
			if (key.renewOnReconnect)
				renew.push(key);
		});
		//subscriptions = [];
		renew.forEach(function(key) {
			//proto.on(key.channel, key.callback);
			proto.on(key);
		});
	};

	// Make an Text ID so the localStorage will be filled by something
	proto.makeResourceId= function()
	{
		var text = "";
		var possible = "abcdefghijklmnopqrstuvwxyz0123456789";

		for( var i=0; i < 5; i++ )
			text += possible.charAt(Math.floor(Math.random() * possible.length));

		localStorage['resource']= text;
	}

	/*
		Connect to ZetaPush
		connectionData must be given by a Authent Object
	*/
	proto.connect= function(connectionData){

		_connectionData= connectionData;

		cometd.handshake(connectionData);
	};

	/*
		Reconnect
	*/
	proto.reconnect= function(){
		connect(_connectionData);
	}

	/**
	 * Reverts the global {@link ZetaPush} to its previous value and returns a reference to this version.
	 *
	 * @return {Function} Non conflicting ZetaPush class.
	 */
	ZetaPush.noConflict = function noConflict() {
		exports.ZetaPush = originalGlobalValue;
		return zp;
	};

	// Expose the class either via AMD, CommonJS or the global object
	if (typeof define === 'function' && define.amd) {
		define(function () {
			return zp;
		});
	}
	else if (typeof module === 'object' && module.exports){
		module.exports = zp;
	}
	else {
		exports.zetaPush = zp;
	}
}.call(this));
<commit_msg>change zetaPush to zp add namespace ZetaPush, ZetaPush.service, ZetaPush.authent add businessId in the Init function<commit_after>/*
	ZetaPushCore v1.0
	Javascript core sdk for ZetaPush
	Mikael Morvan - March 2015
*/

// Global NameSpace
ZetaPush = {};
ZetaPush.service= {};
ZetaPush.authent={};

;(function () {
	'use strict';

	/**
	 * Class for managing core functionnalities.
	 *
	 * @class ZetaPush Manages core functionnalities
	 */
	function ZP() {
	}

	// Singleton for ZetaPush core
	var _zp= new ZP();
	var proto = ZP.prototype;
	var exports = this;
	var originalGlobalValue = exports.ZP;

	var cometd = $.cometd,
	_connectionData= null,
	connected = false,
	_businessId= null,
	_clientId= null,
	subscriptions = [];

	/*
		Listeners for cometD meta channels
	*/
	cometd.addListener('/meta/connect', function(msg) {
		if (cometd.isDisconnected()) {
			connected = false;
			log.info('connection closed');
			return;
		}

		var wasConnected = connected;
		connected = msg.successful;
		if (!wasConnected && connected) { // reconnected
			log.info('connection established');
			cometd.notifyListeners('/meta/connected', msg);
			cometd.batch(function(){
				_zp.refresh();
			});
		} else if (wasConnected && !connected) {
			log.warn('connection broken');
		}
	});

	cometd.addListener('/meta/handshake', function(handshake) {
		if (handshake.successful) {
			log.debug('successful handshake', handshake);
			_clientId = handshake.clientId;
		}
		else {
			log.warn('unsuccessful handshake');
			_clientId = null;
		}
	});

	proto.isConnected= function(){
		return !cometd.isDisconnected();
	}
	/*
		Generate a channel
	*/
	proto.generateChannel= function(deploymentId, verb){
		return '/service/' + _businessId +'/'+ deploymentId +'/'+ verb;
	}

	/*
		Generate a channel
	*/
	proto.generateMetaChannel= function( deploymentId, verb){
		return '/meta/' + _businessId +'/'+ deploymentId +'/'+ verb;
	}

	/*
		Listener for every ZetaPush and CometD events

		Args:
		1 argument: a previous key (for refresh)
		2 arguments: a topic and a callback
		4 arguments: businessId, deploymentId, verb and callback
	*/
	proto.on= function(businessId, deploymentId, verb, callback){
		// One can call the function with a key
		if (arguments.length== 1){
			var key= arguments[0];
		}
		else if (arguments.length == 2){
			var key={};
			key.channel= arguments[0];
			key.callback= arguments[1];
			subscriptions.push(key);
		} else if (arguments.length == 4) {
			var key={};
			key.channel= proto.generateChannel(businessId, deploymentId, verb);
			key.callback= callback;
			subscriptions.push(key);
		} else{
			throw "zetaPush.on - bad arguments";
		}

		var tokens= key.channel.split("/");
		if (tokens.length<=1){
			cometd.notifyListeners('/meta/error', "Syntax error in the channel name");
			return null;
		}

		if (tokens[1]=='service'){
			key.isService= true;

			if (connected) {
				key.sub = cometd.subscribe(key.channel, key.callback);
				log.debug('subscribed', key);
			} else {
				log.debug('queuing subscription request', key);
			}

		} else if (tokens[1]=='meta'){
			key.isService= false;
			key.sub= cometd.addListener(key.channel, key.callback);
		} else {
			log.error("This event can t be managed by ZetaPush", evt);
			return null;
		}
		if (key.renewOnReconnect==null)
			key.renewOnReconnect = true;

		return key;
	}

	/*
		Remove listener
	*/
	proto.off= function(key){
		if (!key || key.sub==null)
			return;

		if (key.isService){
			cometd.unsubscribe(key.sub);
			key.sub= null;
		} else {
			cometd.removeListener(key.sub);
			key.sub= null;
		}
		log.debug('unsubscribed', key);
		key.renewOnReconnect = false;
	}

	/*
		Send data
		3 params
		businessId, deploymentId, verb (no data)
		4 params
		businessId, deploymentId, verb, data
		2 params
		channel, data
	*/
	proto.send= function(businessId, deploymentId, verb, data){

		var evt, sendData;

		if ((arguments.length== 2) || (arguments.length==1)){
			evt= arguments[0];
			sendData= arguments[1];
		}
		else if ((arguments.length==3) || (arguments.length==4)){
			evt= proto.generateChannel(businessId, deploymentId, verb);
			sendData= data;
		}

		var tokens= evt.split("/");
		if (tokens.length<=1){
			cometd.notifyListeners('/meta/error', "Syntax error in the channel name");
			return;
		}

		if (tokens[1]=='service'){
			if (connected){
				cometd.publish(evt, sendData);
			}
		}
		else if (tokens[1]=='meta'){
			cometd.notifyListeners(evt, sendData);
		}
	}

	/*
		Init ZetaPush with the server url
	*/
	proto.init= function(serverUrl, businessId, debugLevel){
		_businessId= businessId;

		log.setLevel(debugLevel);
		if (debugLevel == 'debug')
			cometd.websocketEnabled= false;

		cometd.configure({
			url: serverUrl,
			logLevel: debugLevel,
			backoffIncrement: 100,
			maxBackoff: 500,
			appendMessageTypeToURL: false
		});
	}
	/*
		Disconnect ZetaPush
	*/
	proto.disconnect= function() {
		cometd.disconnect(true);
	}

	// http://cometd.org/documentation/cometd-javascript/subscription
	cometd.onListenerException = function(exception, subscriptionHandle, isListener, message) {
		log.error('Uncaught exception for subscription', subscriptionHandle, ':', exception, 'message:', message);
		if (isListener) {
			cometd.removeListener(subscriptionHandle);
			log.error('removed listener');
		} else {
			cometd.unsubscribe(subscriptionHandle);
			log.error('unsubscribed');
		}
		disconnect();
	};

	/*
		Refresh subscriptions
	*/
	proto.refresh= function() {
		log.debug('refreshing subscriptions');
		var renew = [];
		subscriptions.forEach(function(key) {
			if (key.sub){
				if (key.isService)
					cometd.unsubscribe(key.sub)
				else
					cometd.removeListener(key.sub);
			}
			if (key.renewOnReconnect)
				renew.push(key);
		});
		//subscriptions = [];
		renew.forEach(function(key) {
			//proto.on(key.channel, key.callback);
			proto.on(key);
		});
	};

	/*
		Make a new Resource ID
		Store it in localStorage
	*/
	proto.makeResourceId= function()
	{
		var text = "";
		var possible = "abcdefghijklmnopqrstuvwxyz0123456789";

		for( var i=0; i < 5; i++ )
			text += possible.charAt(Math.floor(Math.random() * possible.length));

		return text;
	}

	/*
		Connect to ZetaPush
		connectionData must be given by an Authent Object
	*/
	proto.connect= function(connectionData){

		_connectionData= connectionData;

		cometd.handshake(connectionData);
	};

	/*
		Reconnect
	*/
	proto.reconnect= function(){
		connect(_connectionData);
	}

	/*
		getBusinessId
	*/
	proto.getBusinessId= function(){
		return _businessId;
	}

	/**
	 * Reverts the global {@link ZetaPush} to its previous value and returns a reference to this version.
	 *
	 * @return {Function} Non conflicting ZetaPush class.
	 */
	ZP.noConflict = function noConflict() {
		exports.ZP = originalGlobalValue;
		return _zp;
	};

	// Expose the class either via AMD, CommonJS or the global object
	if (typeof define === 'function' && define.amd) {
		define(function () {
			return _zp;
		});
	}
	else if (typeof module === 'object' && module.exports){
		module.exports = _zp;
	}
	else {
		exports.zp = _zp;
	}
}.call(this));
<|endoftext|>


<commit_before>requirejs.config({
    paths: {
        "d3": "../extensions/animatedscatterplot/d3.min",
        "dragit": "../extensions/animatedscatterplot/dragit"
    },
    "shim": {
        "dragit": ["d3"]
    }
});

define(["jquery", "./d3.min", "text!./animatedscatterplot.css", "qvangular", "text!./dragit.js"], function ($, d3, css, qv, drag) {
    'use strict';
    $("


<commit_before>const {
  Spin,
  Icon,
  Button,
  Row,
  Col,
  Select,
  Card,
  Affix,
  Modal,
  Result
} = antd;
const { useState, useEffect, useCallback, useMemo } = React;

const INITIAL_DASHBOARD = 0;
const defaultPlotStyle = { height: 350, margin: 5 };

const Dashboard = () => {
  const [dashboards, setDashboards] = useState([]);
  const [dashIndex, setDashIndex] = useState(INITIAL_DASHBOARD);

  const handleClick = useCallback(index => {
    setDashIndex(index);
  }, []);

  useEffect(() => {
    axios
      .get("http://localhost:5050/api/dashboard/")
      .then(response => setDashboards(response.data));
  }, []);

  if (!dashboards.length) return null;

  const { layout, plots } = dashboards[dashIndex];

  return (

        <DashSelect setDashIndex={handleClick} dashboards={dashboards} />

        {layout === "row" ? (
          <RowLayout dashIndex={dashIndex} plots={plots} />
        ) : (
          <GridLayout dashIndex={dashIndex} plots={plots} />
        )}

  );
};

const DashSelect = ({ setDashIndex, dashboards }) => {
  const dashList = useMemo(
    () =>
      dashboards.map((dashboard, index) => (
        <option key={index} value={index}>
          {dashboard.name}
        </option>
      )),
    [dashboards]
  );

  return (
    <Affix offsetTop={30}>
      <Select
        defaultValue={INITIAL_DASHBOARD}
        style={{ width: 200 }}
        onChange={value => setDashIndex(value)}
      >
        {dashList}
      </Select>
    </Affix>
  );
};

const ModalAddPlot = ({ visible, setVisible }) => {
  const handleOk = e => {
    setVisible(false);
  };

  const handleCancel = e => {
    setVisible(false);
  };

  return (
    <Modal
      title="Add plot"
      visible={visible}
      onOk={handleOk}
      onCancel={handleCancel}
    >

Some contents...

    </Modal>
  );
};

const RowLayout = ({ dashIndex, plots }) => {
  if (!plots.length) return
No plots found
;
  const [modalAdd, setModelAdd] = useState(false);

  return (
    <React.Fragment>
      <ModalAddPlot visible={modalAdd} setVisible={setModelAdd} />
      {plots.map((data, index) => (
        <Row key={`${dashIndex}-${index}-${data.scenario}`}>
          <Col>
            <Plot index={index} dashIndex={dashIndex} data={data} />
          </Col>
        </Row>
      ))}
      <Affix offsetBottom={100}>
        <Button type="primary" style={{ float: "right" }} onClick={setModelAdd}>
          + Add plot
        </Button>
      </Affix>
    </React.Fragment>
  );
};

const GridLayout = ({ dashIndex, plots }) => {
  if (!plots.length) return
No plots found
;

  return (
    <React.Fragment>

        {plots.map((data, index) => (
          <div
            className="col-lg-4 col-md-12 col-sm-12 col-xs-12 plot-widget"
            key={`${dashIndex}-${index}-${data.scenario}`}
          >
            <Plot index={index} dashIndex={dashIndex} data={data} />

        ))}

    </React.Fragment>
  );
};

const Plot = ({ index, dashIndex, data, style }) => {
  const [div, setDiv] = useState(null);
  const [error, setError] = useState(null);
  const [hasIntersected, setIntersected] = useState(false);

  const plotStyle = { ...defaultPlotStyle, ...style };

  // TODO: Maybe find a better solution
  const hash = `cea-react-${dashIndex}-${index}`;

  // Get plot div
  useEffect(() => {
    axios
      .get(`http://localhost:5050/plots/div/${dashIndex}/${index}`)
      .then(response => {
        setDiv(() => {
          let script = null;
          let content = HTMLReactParser(response.data, {
            replace: function(domNode) {
              if (domNode.type === "script" && domNode.children[0]) {
                script = domNode.children[0].data;
              }
            }
          });
          return { content, script };
        });
      })
      .catch(_error => {
        setError(_error.response);
      });

    // Clean up script nodes on unmount
    return () => {
      let script = document.querySelector(`script[data-id=script-${hash}]`);
      if (script) script.remove();
    };
  }, []);

  // Mount script node when div is mounted
  useEffect(() => {
    if (div) {
      var _script = document.createElement("script");
      _script.dataset.id = `script-${hash}`;
      document.body.appendChild(_script);
      _script.append(div.script);
    }
  }, [div]);

  return (
    <Card
      title={

          {data.title}
          {data.scenario && (
            <React.Fragment>
               -
              {data.scenario}
            </React.Fragment>
          )}

      }
      extra="Test"
      style={{ ...plotStyle, height: "" }}
      bodyStyle={{ height: plotStyle.height }}
      size="small"
    >
      {div ? (
        div.content
      ) : error ? (
        <ErrorPlot error={error} />
      ) : (
        <LoadingPlot plotStyle={plotStyle} />
      )}
    </Card>
  );
};

const LoadingPlot = ({ plotStyle }) => {
  return (
    <Spin
      indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />}
      tip="Loading Plot..."
    >

    </Spin>
  );
};

const ErrorPlot = ({ error }) => {
  console.log(error.status);
  if (error.status === 404) return HTMLReactParser(error.data);
  if (error.status === 500)
    return (
      <Result
        status="error"
        icon="Something went wrong!"
        title={error.status}
        subTitle={error.statusText}
      >

{error.data}

      </Result>
    );
  return null;
};

const EmptyPlot = () => {
  return
EmptyPlot
;
};

ReactDOM.render(
  React.createElement(Dashboard),
  document.querySelector("#cea-dashboard")
);

// To enable resize of Plotly plots
window.addEventListener("resize", function() {
  console.log("resizing");
  $.each($(".plotly-graph-div.js-plotly-plot"), function() {
    Plotly.Plots.resize($(this).attr("id"));
  });
});

document.getElementById("menu_toggle").addEventListener("click", function() {
  console.log("resizing");
  $.each($(".plotly-graph-div.js-plotly-plot"), function() {
    Plotly.Plots.resize($(this).attr("id"));
  });
});
<commit_msg>Add 'add dashboard' button and modal<commit_after>const {
  Spin,
  Icon,
  Button,
  Row,
  Col,
  Select,
  Card,
  Affix,
  Modal,
  Result
} = antd;
const { useState, useEffect, useCallback, useMemo } = React;

const INITIAL_DASHBOARD = 0;
const defaultPlotStyle = { height: 350, margin: 5 };

const Dashboard = () => {
  const [dashboards, setDashboards] = useState([]);
  const [dashIndex, setDashIndex] = useState(INITIAL_DASHBOARD);
  const [showModalNew, setShowModalNew] = useState(false);

  const handleSelect = useCallback(index => {
    setDashIndex(index);
  }, []);

  useEffect(() => {
    axios
      .get("http://localhost:5050/api/dashboard/")
      .then(response => setDashboards(response.data));
  }, []);

  if (!dashboards.length) return null;

  const { layout, plots } = dashboards[dashIndex];

  return (
    <React.Fragment>

          <DashSelect setDashIndex={handleSelect} dashboards={dashboards} />

            <Button
              type="primary"
              icon="plus"
              size="small"
              onClick={setShowModalNew}
            >
              New Dashboard
            </Button>

          {layout === "row" ? (
            <RowLayout dashIndex={dashIndex} plots={plots} />
          ) : (
            <GridLayout dashIndex={dashIndex} plots={plots} />
          )}

      <ModalNewDashboard visible={showModalNew} setVisible={setShowModalNew} />
    </React.Fragment>
  );
};

const DashSelect = ({ setDashIndex, dashboards }) => {
  const dashList = useMemo(
    () =>
      dashboards.map((dashboard, index) => (
        <option key={index} value={index}>
          {dashboard.name}
        </option>
      )),
    [dashboards]
  );

  return (
    <Affix offsetTop={30}>
      <Select
        defaultValue={INITIAL_DASHBOARD}
        style={{ width: 200 }}
        onChange={value => setDashIndex(value)}
      >
        {dashList}
      </Select>
    </Affix>
  );
};

const ModalAddPlot = ({ visible, setVisible }) => {
  const handleOk = e => {
    setVisible(false);
  };

  const handleCancel = e => {
    setVisible(false);
  };

  return (
    <Modal
      title="Add plot"
      visible={visible}
      onOk={handleOk}
      onCancel={handleCancel}
    >

Some contents...

    </Modal>
  );
};

const ModalNewDashboard = ({ visible, setVisible }) => {
  const handleOk = e => {
    setVisible(false);
  };

  const handleCancel = e => {
    setVisible(false);
  };

  return (
    <Modal
      title="New Dashboard"
      visible={visible}
      onOk={handleOk}
      onCancel={handleCancel}
    >

Some contents...

    </Modal>
  );
};

const RowLayout = ({ dashIndex, plots }) => {
  const [showModalAdd, setShowModalAdd] = useState(false);

  return (
    <React.Fragment>
      {plots.map((data, index) => (
        <Row key={`${dashIndex}-${index}-${data.scenario}`}>
      <ModalAddPlot visible={showModalAdd} setVisible={setShowModalAdd} />
          <Col>
            <Plot index={index} dashIndex={dashIndex} data={data} />
          </Col>
        </Row>
      ))}
      <Affix offsetBottom={100}>
        <Button
          type="primary"
          icon="plus"
          style={{ float: "right" }}
          onClick={setShowModalAdd}
        >
          Add plot
        </Button>
      </Affix>
    </React.Fragment>
  );
};

const GridLayout = ({ dashIndex, plots }) => {
  if (!plots.length) return
No plots found
;

  return (
    <React.Fragment>

        {plots.map((data, index) => (
          <div
            className="col-lg-4 col-md-12 col-sm-12 col-xs-12 plot-widget"
            key={`${dashIndex}-${index}-${data.scenario}`}
          >
            <Plot index={index} dashIndex={dashIndex} data={data} />

        ))}

    </React.Fragment>
  );
};

const Plot = ({ index, dashIndex, data, style }) => {
  const [div, setDiv] = useState(null);
  const [error, setError] = useState(null);
  const [hasIntersected, setIntersected] = useState(false);

  const plotStyle = { ...defaultPlotStyle, ...style };

  // TODO: Maybe find a better solution
  const hash = `cea-react-${dashIndex}-${index}`;

  // Get plot div
  useEffect(() => {
    axios
      .get(`http://localhost:5050/plots/div/${dashIndex}/${index}`)
      .then(response => {
        setDiv(() => {
          let script = null;
          let content = HTMLReactParser(response.data, {
            replace: function(domNode) {
              if (domNode.type === "script" && domNode.children[0]) {
                script = domNode.children[0].data;
              }
            }
          });
          return { content, script };
        });
      })
      .catch(_error => {
        setError(_error.response);
      });

    // Clean up script nodes on unmount
    return () => {
      let script = document.querySelector(`script[data-id=script-${hash}]`);
      if (script) script.remove();
    };
  }, []);

  // Mount script node when div is mounted
  useEffect(() => {
    if (div) {
      var _script = document.createElement("script");
      _script.dataset.id = `script-${hash}`;
      document.body.appendChild(_script);
      _script.append(div.script);
    }
  }, [div]);

  return (
    <Card
      title={

          {data.title}
          {data.scenario && (
            <React.Fragment>
               -
              {data.scenario}
            </React.Fragment>
          )}

      }
      extra="Test"
      style={{ ...plotStyle, height: "" }}
      bodyStyle={{ height: plotStyle.height }}
      size="small"
    >
      {div ? (
        div.content
      ) : error ? (
        <ErrorPlot error={error} />
      ) : (
        <LoadingPlot plotStyle={plotStyle} />
      )}
    </Card>
  );
};

const LoadingPlot = ({ plotStyle }) => {
  return (
    <Spin
      indicator={<Icon type="loading" style={{ fontSize: 24 }} spin />}
      tip="Loading Plot..."
    >

    </Spin>
  );
};

const ErrorPlot = ({ error }) => {
  console.log(error.status);
  if (error.status === 404) return HTMLReactParser(error.data);
  if (error.status === 500)
    return (
      <Result
        status="error"
        icon="Something went wrong!"
        title={error.status}
        subTitle={error.statusText}
      >

{error.data}

      </Result>
    );
  return null;
};

const EmptyPlot = () => {
  return
EmptyPlot
;
};

ReactDOM.render(
  React.createElement(Dashboard),
  document.querySelector("#cea-dashboard")
);

// To enable resize of Plotly plots
window.addEventListener("resize", function() {
  console.log("resizing");
  $.each($(".plotly-graph-div.js-plotly-plot"), function() {
    Plotly.Plots.resize($(this).attr("id"));
  });
});

document.getElementById("menu_toggle").addEventListener("click", function() {
  console.log("resizing");
  $.each($(".plotly-graph-div.js-plotly-plot"), function() {
    Plotly.Plots.resize($(this).attr("id"));
  });
});
<|endoftext|>


<commit_before>'use strict'

const { Rat } = require('../db')
const RatQuery = require('../Query/RatQuery')
const { CustomPresenter } = require('../classes/Presenters')
const APIEndpoint = require('../APIEndpoint')
const { Ships } = require('./ship')
const { NotFoundAPIError } = require('../APIError')

class Rats extends APIEndpoint {
  async search (ctx) {
    let ratsQuery = new RatQuery(ctx.query, ctx)
    let result = await Rat.findAndCountAll(ratsQuery.toSequelize)
    return Rats.presenter.render(result.rows, ctx.meta(result, ratsQuery))
  }

  async findById (ctx) {
    let ratQuery = new RatQuery({id: ctx.params.id}, ctx)
    let result = await Rat.findAndCountAll(ratQuery.toSequelize)

    return Rats.presenter.render(result.rows, ctx.meta(result, ratQuery))
  }

  async create (ctx) {
    this.requireWritePermission(ctx, ctx.data)

    if (!ctx.data.userId) {
      ctx.data.userId = ctx.state.user.data.id
    }

    let result = await Rat.create(ctx.data)

    ctx.response.status = 201
    let renderedResult = Rats.presenter.render(result, ctx.meta(result))
    process.emit('ratCreated', ctx, renderedResult)
    return renderedResult
  }

  async update (ctx) {
    this.requireWritePermission(ctx, ctx.data)

    let rat = await Rat.findOne({
      where: { id: ctx.params.id }
    })

    if (!rat) {
      throw new NotFoundAPIError({ parameter: 'id' })
    }

    this.requireWritePermission(ctx, rat)

    await Rat.update(ctx.data, {
      where: {
        id: ctx.params.id
      }
    })

    let ratQuery = new RatQuery({id: ctx.params.id}, ctx)
    let result = await Rat.findAndCountAll(ratQuery.toSequelize)
    let renderedResult = Rats.presenter.render(result.rows, ctx.meta(result, ratQuery))
    process.emit('ratUpdated', ctx, renderedResult)
    return renderedResult
  }

  async delete (ctx) {
    let rat = await Rat.findOne({
      where: {
        id: ctx.params.id
      }
    })

    if (!rat) {
      throw new NotFoundAPIError({ parameter: 'id' })
    }

    rat.destroy()

    process.emit('ratDeleted', ctx, CustomPresenter.render({
      id: ctx.params.id
    }))
    ctx.status = 204
    return true
  }

  getReadPermissionForEntity (ctx, entity) {
    if (entity.userId === ctx.state.user.data.id) {
      return ['rat.write', 'rat.write.me']
    }
    return ['rat.write']
  }

  getWritePermissionForEntity (ctx, entity) {
    if (entity.userId === ctx.state.user.data.id) {
      return ['rat.write', 'rat.write.me']
    }
    return ['rat.write']
  }

  static get presenter () {
    class RatsPresenter extends APIEndpoint.presenter {
      relationships () {
        return {
          ships: Ships.presenter
        }
      }
    }
    RatsPresenter.prototype.type = 'rats'
  }
}

module.exports = Rats
<commit_msg>Fix failure to generate endpoint responses caused by a missing return in getter<commit_after>'use strict'

const { Rat } = require('../db')
const RatQuery = require('../Query/RatQuery')
const { CustomPresenter } = require('../classes/Presenters')
const APIEndpoint = require('../APIEndpoint')
const Ships = require('./ship')
const { NotFoundAPIError } = require('../APIError')

class Rats extends APIEndpoint {
  async search (ctx) {
    let ratsQuery = new RatQuery(ctx.query, ctx)
    let result = await Rat.findAndCountAll(ratsQuery.toSequelize)
    return Rats.presenter.render(result.rows, ctx.meta(result, ratsQuery))
  }

  async findById (ctx) {
    let ratQuery = new RatQuery({id: ctx.params.id}, ctx)
    let result = await Rat.findAndCountAll(ratQuery.toSequelize)

    return Rats.presenter.render(result.rows, ctx.meta(result, ratQuery))
  }

  async create (ctx) {
    this.requireWritePermission(ctx, ctx.data)

    if (!ctx.data.userId) {
      ctx.data.userId = ctx.state.user.data.id
    }

    let result = await Rat.create(ctx.data)

    ctx.response.status = 201
    let renderedResult = Rats.presenter.render(result, ctx.meta(result))
    process.emit('ratCreated', ctx, renderedResult)
    return renderedResult
  }

  async update (ctx) {
    this.requireWritePermission(ctx, ctx.data)

    let rat = await Rat.findOne({
      where: { id: ctx.params.id }
    })

    if (!rat) {
      throw new NotFoundAPIError({ parameter: 'id' })
    }

    this.requireWritePermission(ctx, rat)

    await Rat.update(ctx.data, {
      where: {
        id: ctx.params.id
      }
    })

    let ratQuery = new RatQuery({id: ctx.params.id}, ctx)
    let result = await Rat.findAndCountAll(ratQuery.toSequelize)
    let renderedResult = Rats.presenter.render(result.rows, ctx.meta(result, ratQuery))
    process.emit('ratUpdated', ctx, renderedResult)
    return renderedResult
  }

  async delete (ctx) {
    let rat = await Rat.findOne({
      where: {
        id: ctx.params.id
      }
    })

    if (!rat) {
      throw new NotFoundAPIError({ parameter: 'id' })
    }

    rat.destroy()

    process.emit('ratDeleted', ctx, CustomPresenter.render({
      id: ctx.params.id
    }))
    ctx.status = 204
    return true
  }

  getReadPermissionForEntity (ctx, entity) {
    if (entity.userId === ctx.state.user.data.id) {
      return ['rat.write', 'rat.write.me']
    }
    return ['rat.write']
  }

  getWritePermissionForEntity (ctx, entity) {
    if (entity.userId === ctx.state.user.data.id) {
      return ['rat.write', 'rat.write.me']
    }
    return ['rat.write']
  }

  static get presenter () {
    class RatsPresenter extends APIEndpoint.presenter {
      relationships () {
        return {
          ships: Ships.presenter
        }
      }
    }
    RatsPresenter.prototype.type = 'rats'
    return RatsPresenter
  }
}

module.exports = Rats
<|endoftext|>


<commit_before>var keystone = require('keystone');
var bcrypt = require('bcrypt');
var redis = require('redis');
var async = require('async');

var Text = keystone.list('Text'),
	Ballot = keystone.list('Ballot'),
	Source = keystone.list('Source'),
	Like = keystone.list('Like'),

	TextHelper = require('../../helpers/TextHelper'),
	LikeHelper = require('../../helpers/LikeHelper');

/**
 * List Texts
 */
exports.list = function(req, res)
{
	Text.model.find()
		.exec(function(err, texts)
	    {
			if (err)
				return res.apiError('database error', err);

			res.apiResponse({ texts: TextHelper.filterReadableTexts(texts, req, true) });
		});
}

/**
 * Get Text by ID
 */
exports.get = function(req, res)
{
	Text.model.findById(req.params.id)
		.populate('likes')
		.exec(function(err, text)
    {
		if (err)
			return res.apiError('database error', err);

		if (!text)
			return res.apiError('not found');

		if (!TextHelper.textIsReadable(text, req))
			return res.status(403).send();

		text.likes = LikeHelper.filterUserLikes(text.likes, req.user);

		res.apiResponse({ text: text });
	});
}

exports.latest = function(req, res)
{
	Text.model.find()
		.sort('-publishedAt')
		.limit(10)
		.exec(function(err, texts)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!texts)
				return res.apiError('not found');

			res.apiResponse({ texts: TextHelper.filterReadableTexts(texts, req, true) });
		});
}

exports.getBallot = function(req, res)
{
	Ballot.getByTextIdAndVoter(
		req.params.id,
		req.user.sub,
		function(err, ballot)
		{
			if (err)
				return res.apiError('database error', err);

			if (!ballot)
				return res.status(404).apiResponse({
					error: 'ballot does not exist'
				});

			return res.apiResponse({ ballot: ballot });
		}
	);
}

/**
 * Get Text by slug
 */
exports.getBySlug = function(req, res)
{
	Text.model.findOne()
		.where('slug', req.params.slug)
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!text)
				return res.status(404).send();
			if (!TextHelper.textIsReadable(text, req))
				return res.status(403).send();

			res.apiResponse({ text: text });
		});
}

function updateTextSources(user, text, next)
{
    var mdLinkRegex = new RegExp(/\[([^\[]+)\]\(([^\)]+)\)/g);
    var ops = [function(callback) { callback(null, []); }];

    while (match = mdLinkRegex.exec(text.content.md))
    {
        (function(url) {
            ops.push(function(result, callback)
            {
                // FIXME: do not fetch pages that are already listed in the text sources
                Source.fetchPageTitle(url, function(err, title)
                {
                    if (err)
                        result.push({url: url, title: ''});
                    else
                        result.push({url: url, title: title});

                    callback(null, result);
                });
            });
        })(match[2]);
    }

    async.waterfall(ops, function(error, result)
    {
        var saveOps = [
            function(callback)
            {
				var urls = result.map(function(sourceData) { return sourceData.url; });
                Source.model.find({text: text, auto: true, url: {$nin : urls}})
					.remove(function(err)
	                {
	                    callback(err);
	                });
            }
        ];

        if (error)
        {
            console.log(error);
            next(error); // FIXME: retry later
        }
        else
        {
            saveOps = saveOps.concat(result.map(function(sourceData)
            {
                return function(callback)
                {
                    Source.model.findOne({url : sourceData.url})
                        .exec(function(err, source)
                        {
                            if (err)
                                return callback(err);

                            if (source && source.title == sourceData.title)
                                return callback(err);

                            if (!source)
                            {
                                source = Source.model({
                                    title: sourceData.title,
                                    url: sourceData.url,
                                    auto: true,
                                    author: bcrypt.hashSync(user.sub, 10),
                                    text: text
                                });
                            }

                            source.save(function(err)
                            {
                                return callback(err);
                            });
                        });
                };
            }));

            async.waterfall(saveOps, function(error)
            {
                next();
            });
        }
    });
}

exports.save = function(req, res)
{
	if (!req.body.title)
		return res.status(400).apiResponse({ error : 'missing title' });

	if (!req.body.content)
		return res.status(400).apiResponse({ error : 'missing content' });

	var newText = Text.model({
		title: req.body.title,
		content: { md: req.body.content }
	});

	Text.model.findOne(req.body.id ? {_id : req.body.id} : {slug: newText.slug})
		.select('-likes')
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (text && !TextHelper.textIsReadable(text, req))
				return res.status(403).send();

			if (!text)
			{
				updateTextSources(req.user, newText, function(err)
				{
					newText.author = bcrypt.hashSync(req.user.sub, 10);
					newText.save(function(err, text)
					{
						if (err)
							return res.apiError('database error', err);

						return res.apiResponse({
							action: 'create',
							text : newText
						})
					});
				});
			}
			else
			{
				if (bcrypt.compareSync(req.user.sub, text.author))
				{
					text.title = newText.title;
					text.content.md = newText.content.md;
					updateTextSources(req.user, text, function(err)
					{
						text.save(function(err, text)
						{
							if (err)
								return res.apiError('database error', err);

							delete text.likes;

							return res.apiResponse({
								action: 'update',
								text : text
							});
						});
					});
				}
				else
					return res.status(403).send();
			}
		});
}

// exports.delete = function(req, res)
// {
// 	Text.model.findById(req.params.id).remove(function(err)
// 	{
// 		if (err)
// 			return res.apiError('database error', err);
//
// 		return res.apiResponse({action: 'deleted'});
// 	});
// }

exports.status = function(req, res)
{
	Text.model.findById(req.params.id)
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!text)
				return res.status(404).send();
			if (!bcrypt.compareSync(req.user.sub, text.author))
				return res.status(403).send();

			text.status = req.params.status;
			text.save(function(err)
			{
				if (err)
					return res.apiError('database error', err);

				return res.apiResponse({
					action: 'update',
					text : text
				});
			});
		});
}

exports.addLike = function(req, res)
{
    LikeHelper.addLike(
        Text.model, req.params.id, req.user, req.params.value == 'true',
        function(err, resource, like)
        {
            if (err)
                return res.apiError('database error', err);

            if (!resource)
                return res.status(404).apiResponse({
					error: 'error.ERROR_TEXT_NOT_FOUND'
				});

            if (like)
                return res.status(400).apiResponse({
                    error: 'error.ERROR_TEXT_ALREADY_LIKED'
                });
        },
        function(resource, like)
        {
            return res.apiResponse({ like : like });
        }
    );
}

exports.removeLike = function(req, res)
{
    LikeHelper.removeLike(
        Text.model, req.params.id, req.user,
        function(err, resource, like)
        {
            if (err)
                return res.apiError('database error', err);

            if (!resource || !like)
                return res.status(404).apiResponse();
        },
        function(resource, like)
        {
            res.apiResponse({ like : like });
        }
    );
}
<commit_msg>/api/text/status will now update Text.publishedAt when status == 'published'.<commit_after>var keystone = require('keystone');
var bcrypt = require('bcrypt');
var redis = require('redis');
var async = require('async');

var Text = keystone.list('Text'),
	Ballot = keystone.list('Ballot'),
	Source = keystone.list('Source'),
	Like = keystone.list('Like'),

	TextHelper = require('../../helpers/TextHelper'),
	LikeHelper = require('../../helpers/LikeHelper');

/**
 * List Texts
 */
exports.list = function(req, res)
{
	Text.model.find()
		.exec(function(err, texts)
	    {
			if (err)
				return res.apiError('database error', err);

			res.apiResponse({ texts: TextHelper.filterReadableTexts(texts, req, true) });
		});
}

/**
 * Get Text by ID
 */
exports.get = function(req, res)
{
	Text.model.findById(req.params.id)
		.populate('likes')
		.exec(function(err, text)
    {
		if (err)
			return res.apiError('database error', err);

		if (!text)
			return res.apiError('not found');

		if (!TextHelper.textIsReadable(text, req))
			return res.status(403).send();

		text.likes = LikeHelper.filterUserLikes(text.likes, req.user);

		res.apiResponse({ text: text });
	});
}

exports.latest = function(req, res)
{
	Text.model.find()
		.sort('-publishedAt')
		.limit(10)
		.exec(function(err, texts)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!texts)
				return res.apiError('not found');

			res.apiResponse({ texts: TextHelper.filterReadableTexts(texts, req, true) });
		});
}

exports.getBallot = function(req, res)
{
	Ballot.getByTextIdAndVoter(
		req.params.id,
		req.user.sub,
		function(err, ballot)
		{
			if (err)
				return res.apiError('database error', err);

			if (!ballot)
				return res.status(404).apiResponse({
					error: 'ballot does not exist'
				});

			return res.apiResponse({ ballot: ballot });
		}
	);
}

/**
 * Get Text by slug
 */
exports.getBySlug = function(req, res)
{
	Text.model.findOne()
		.where('slug', req.params.slug)
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!text)
				return res.status(404).send();
			if (!TextHelper.textIsReadable(text, req))
				return res.status(403).send();

			res.apiResponse({ text: text });
		});
}

function updateTextSources(user, text, next)
{
    var mdLinkRegex = new RegExp(/\[([^\[]+)\]\(([^\)]+)\)/g);
    var ops = [function(callback) { callback(null, []); }];

    while (match = mdLinkRegex.exec(text.content.md))
    {
        (function(url) {
            ops.push(function(result, callback)
            {
                // FIXME: do not fetch pages that are already listed in the text sources
                Source.fetchPageTitle(url, function(err, title)
                {
                    if (err)
                        result.push({url: url, title: ''});
                    else
                        result.push({url: url, title: title});

                    callback(null, result);
                });
            });
        })(match[2]);
    }

    async.waterfall(ops, function(error, result)
    {
        var saveOps = [
            function(callback)
            {
				var urls = result.map(function(sourceData) { return sourceData.url; });
                Source.model.find({text: text, auto: true, url: {$nin : urls}})
					.remove(function(err)
	                {
	                    callback(err);
	                });
            }
        ];

        if (error)
        {
            console.log(error);
            next(error); // FIXME: retry later
        }
        else
        {
            saveOps = saveOps.concat(result.map(function(sourceData)
            {
                return function(callback)
                {
                    Source.model.findOne({url : sourceData.url})
                        .exec(function(err, source)
                        {
                            if (err)
                                return callback(err);

                            if (source && source.title == sourceData.title)
                                return callback(err);

                            if (!source)
                            {
                                source = Source.model({
                                    title: sourceData.title,
                                    url: sourceData.url,
                                    auto: true,
                                    author: bcrypt.hashSync(user.sub, 10),
                                    text: text
                                });
                            }

                            source.save(function(err)
                            {
                                return callback(err);
                            });
                        });
                };
            }));

            async.waterfall(saveOps, function(error)
            {
                next();
            });
        }
    });
}

exports.save = function(req, res)
{
	if (!req.body.title)
		return res.status(400).apiResponse({ error : 'missing title' });

	if (!req.body.content)
		return res.status(400).apiResponse({ error : 'missing content' });

	var newText = Text.model({
		title: req.body.title,
		content: { md: req.body.content }
	});

	Text.model.findOne(req.body.id ? {_id : req.body.id} : {slug: newText.slug})
		.select('-likes')
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (text && !TextHelper.textIsReadable(text, req))
				return res.status(403).send();

			if (!text)
			{
				updateTextSources(req.user, newText, function(err)
				{
					newText.author = bcrypt.hashSync(req.user.sub, 10);
					newText.save(function(err, text)
					{
						if (err)
							return res.apiError('database error', err);

						return res.apiResponse({
							action: 'create',
							text : newText
						})
					});
				});
			}
			else
			{
				if (bcrypt.compareSync(req.user.sub, text.author))
				{
					text.title = newText.title;
					text.content.md = newText.content.md;
					updateTextSources(req.user, text, function(err)
					{
						text.save(function(err, text)
						{
							if (err)
								return res.apiError('database error', err);

							delete text.likes;

							return res.apiResponse({
								action: 'update',
								text : text
							});
						});
					});
				}
				else
					return res.status(403).send();
			}
		});
}

// exports.delete = function(req, res)
// {
// 	Text.model.findById(req.params.id).remove(function(err)
// 	{
// 		if (err)
// 			return res.apiError('database error', err);
//
// 		return res.apiResponse({action: 'deleted'});
// 	});
// }

exports.status = function(req, res)
{
	Text.model.findById(req.params.id)
		.exec(function(err, text)
	    {
			if (err)
				return res.apiError('database error', err);
			if (!text)
				return res.status(404).send();
			if (!bcrypt.compareSync(req.user.sub, text.author))
				return res.status(403).send();

			if (req.params.status == 'published' && text.status != 'published')
				text.publishedAt = Date.now();

			text.status = req.params.status;
			text.save(function(err)
			{
				if (err)
					return res.apiError('database error', err);

				return res.apiResponse({
					action: 'update',
					text : text
				});
			});
		});
}

exports.addLike = function(req, res)
{
    LikeHelper.addLike(
        Text.model, req.params.id, req.user, req.params.value == 'true',
        function(err, resource, like)
        {
            if (err)
                return res.apiError('database error', err);

            if (!resource)
                return res.status(404).apiResponse({
					error: 'error.ERROR_TEXT_NOT_FOUND'
				});

            if (like)
                return res.status(400).apiResponse({
                    error: 'error.ERROR_TEXT_ALREADY_LIKED'
                });
        },
        function(resource, like)
        {
            return res.apiResponse({ like : like });
        }
    );
}

exports.removeLike = function(req, res)
{
    LikeHelper.removeLike(
        Text.model, req.params.id, req.user,
        function(err, resource, like)
        {
            if (err)
                return res.apiError('database error', err);

            if (!resource || !like)
                return res.status(404).apiResponse();
        },
        function(resource, like)
        {
            res.apiResponse({ like : like });
        }
    );
}
<|endoftext|>


<commit_before>var request = require("request-promise"),
  moment = require('moment'),
  NodeCache = require('node-cache'),
  _ = require('lodash'),
  left = 600,
  resetTime = moment().add(600, "seconds");
var cache = new NodeCache({stdTTL: 3480}); // Cached tokens expire after 58 minutes, leave a bit of breathing room in case stuff is slow

exports.refreshToken = async function(refreshToken) {
  let token = await cache.get(refreshToken);
  if (token) {
    return token;
  }
  var data = "grant_type=refresh_token&refresh_token=" + refreshToken;
  var auth = "Basic " + new Buffer(sails.config.reddit.clientID + ":" + sails.config.reddit.clientIDSecret).toString("base64");
  var body = await request.post({
    url: 'https://www.reddit.com/api/v1/access_token',
    body: data,
    json: true,
    headers: {
      "Authorization": auth,
      "User-Agent": sails.config.reddit.userAgent,
      "Content-Type": "application/x-www-form-urlencoded",
      "Content-Length": data.length
    }
  }).catch(function (error) {
    throw {statusCode: 502, error: 'Error retrieving token; Reddit responded with status code ' + error.statusCode};
  });
  if (body && body.access_token) {
    cache.set(refreshToken, body.access_token);
    return body.access_token;
  } else {
    throw "Error retrieving token";
  }
};

var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold, silenceErrors) {
  if (left < rateLimitRemainingThreshold && moment().isBefore(resetTime)) {
    throw {statusCode: 504, error: "Rate limited"};
  }
  // Prevent Reddit from sanitizing '> < &' to '> < &' in the response
  url += (url.indexOf('?') === -1 ? '?' : '&') + 'raw_json=1';
  var headers = {"User-Agent": sails.config.reddit.userAgent};
  if (url.indexOf("oauth.reddit.com") !== -1) {
    headers.Authorization = "bearer " + await exports.refreshToken(refreshToken);
  }
  var options = {
    url: url,
    headers: headers,
    resolveWithFullResponse: true,
    method: requestType,
    formData: data
  };
  let response = await request(options).catch(function (error) {
    if (!silenceErrors) {
      sails.log.error('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode);
      sails.log.error('Form data sent: ' + JSON.stringify(data));
    }
    throw {statusCode: error.statusCode, error: '(Reddit response)'};
  });
  updateRateLimits(response);
  var bodyJson;
  try {
    bodyJson = JSON.parse(response.body);
  } catch (error) {
    sails.log.error("Error with parsing: " + response.body);
    throw {error: "Error with parsing: " + response.body};
  }
  return bodyJson;
};

var getEntireListing = async function (refreshToken, endpoint, query, rateThreshold, after, before) {
  var url = endpoint + query + (query ? '&' : '?') + 'count=102&limit=100' + (after ? '&after=' + after : '') + (before ? '&before=' + before : '');
  var batch = await makeRequest(refreshToken, 'GET', url, undefined, rateThreshold, after, before);
  var results = batch.data.children;
  after = before ? undefined : batch.data.after;
  before = before ? batch.data.before : undefined;
  if (!after && !before) {
    return results;
  }
  return _.union(results, await getEntireListing(refreshToken, endpoint, query, rateThreshold, after, before));
};

exports.getFlair = async function (refreshToken, user, subreddit) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flairselector';
  var data = {name: user};
  //Return a Promise containing `response.current` (the flair itself) instead of `response` (the object which contains the flair).
  let res = await makeRequest(refreshToken, 'POST', url, data, 20);
  return res.current;
};

exports.getBothFlairs = async function (refreshToken, user) {
  var ptradesFlairPromise = exports.getFlair(refreshToken, user, 'pokemontrades');
  var svexFlairPromise = exports.getFlair(refreshToken, user, 'SVExchange');
  return Promise.all([ptradesFlairPromise, svexFlairPromise]);
};

exports.setUserFlair = function (refreshToken, name, cssClass, text, subreddit) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/flair';
  var data = {api_type: 'json', css_class: cssClass, name: name, text: text};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.setLinkFlair = function (refreshToken, subreddit, link_id, cssClass, text) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flair';
  var data = {api_type: 'json', css_class: cssClass, link: 't3_' + link_id, text: text};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.checkUsernameAvailable = async function (name) {
  return makeRequest(undefined, 'GET', 'https://www.reddit.com/api/username_available.json?user=' + name, undefined, 10);
};

exports.banUser = function (refreshToken, username, ban_message, note, subreddit, duration) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/friend';
  var data = {api_type: 'json', ban_message: ban_message, duration: (duration ? duration : 'undefined'), name: username, note: note, type: 'banned'};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.getWikiPage = async function (refreshToken, subreddit, page) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/wiki/' + page;
  //Return a Promise for content of the page instead of all the other data
  let res = await makeRequest(refreshToken, 'GET', url, undefined, 5);
  return res.data.content_md;
};

exports.editWikiPage = function (refreshToken, subreddit, page, content, reason) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/wiki/edit';
  var data = {content: content, page: page, reason: reason};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.searchTSVThreads = function (refreshToken, username) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : 'SVExchange';
  var query = "(and (or flair_css_class:'banned' flair_css_class:'sv*') author:'" + username + "')";
  return exports.search(refreshToken, actual_sub, query, true, 'new', 'all', 'cloudsearch');
};

exports.search = function (refreshToken, subreddit, query, restrict_sr, sort, time, syntax) {
  var querystring = '?q=' + encodeURIComponent(query) + (restrict_sr  ? '&restrict_sr=on' : '') +
    (sort ? '&sort=' + sort : '') + (time ? '&t=' + time : '') + '&syntax=' + (syntax ? syntax : 'cloudsearch');
  var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/search';
  return getEntireListing(refreshToken, endpoint, querystring, 10);
};

exports.removePost = function (refreshToken, id, isSpam) {
  var url = 'https://oauth.reddit.com/api/remove';
  var data = {id: 't3_' + id, spam: isSpam};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.lockPost = function (refreshToken, post_id) {
  var url = 'https://oauth.reddit.com/api/lock';
  var data = {id: 't3_' + post_id};
  /* Attempting to lock an archived post results in a 400 response. The request is still considered successful if this happens, so the error is
   * returned instead of being thrown. */
  return makeRequest(refreshToken, 'POST', url, data, 5, true).catch(function (error) {
    if (error.statusCode === 400) {
      return error;
    }
    throw error;
  });
};

exports.markNsfw = function (refreshToken, post_id) {
  var url = 'https://oauth.reddit.com/api/marknsfw';
  var data = {id: 't3_' + post_id};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.sendPrivateMessage = function (refreshToken, subject, text, recipient) {
  var url = 'https://oauth.reddit.com/api/compose';
  var data = {api_type: 'json', subject: subject, text: text, to: recipient};
  return makeRequest(refreshToken, 'POST', url, data, 25);
};

exports.sendReply = function (refreshToken, text, parent_id) {
  var url = 'https://oauth.reddit.com/api/comment';
  var data = {api_type: 'json', text: text, thing_id: parent_id};
  return makeRequest(refreshToken, 'POST', url, data, 30);
};

exports.getModeratorPermissions = async function (refreshToken, username, subreddit) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/about/moderators?user=' + username;
  let res = await makeRequest(refreshToken, 'GET', url, undefined, 5);
  return res.data.children.length ? res.data.children.find(child => child.name === username).mod_permissions : null;
};

exports.getModmail = async function (refreshToken, subreddit, after, before) {
  var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/message/moderator';
  return getEntireListing(refreshToken, endpoint, '', 20, after, before);
};

var updateRateLimits = function (res) {
  if (res && res.headers && res.headers['x-ratelimit-remaining'] && res.headers['x-ratelimit-reset']) {
    left = res.headers['x-ratelimit-remaining'];
    resetTime = moment().add(res.headers['x-ratelimit-reset'], "seconds");
  }
};
<commit_msg>Changing search to lucene (#627)<commit_after>var request = require("request-promise"),
  moment = require('moment'),
  NodeCache = require('node-cache'),
  _ = require('lodash'),
  left = 600,
  resetTime = moment().add(600, "seconds");
var cache = new NodeCache({stdTTL: 3480}); // Cached tokens expire after 58 minutes, leave a bit of breathing room in case stuff is slow

exports.refreshToken = async function(refreshToken) {
  let token = await cache.get(refreshToken);
  if (token) {
    return token;
  }
  var data = "grant_type=refresh_token&refresh_token=" + refreshToken;
  var auth = "Basic " + new Buffer(sails.config.reddit.clientID + ":" + sails.config.reddit.clientIDSecret).toString("base64");
  var body = await request.post({
    url: 'https://www.reddit.com/api/v1/access_token',
    body: data,
    json: true,
    headers: {
      "Authorization": auth,
      "User-Agent": sails.config.reddit.userAgent,
      "Content-Type": "application/x-www-form-urlencoded",
      "Content-Length": data.length
    }
  }).catch(function (error) {
    throw {statusCode: 502, error: 'Error retrieving token; Reddit responded with status code ' + error.statusCode};
  });
  if (body && body.access_token) {
    cache.set(refreshToken, body.access_token);
    return body.access_token;
  } else {
    throw "Error retrieving token";
  }
};

var makeRequest = async function (refreshToken, requestType, url, data, rateLimitRemainingThreshold, silenceErrors) {
  if (left < rateLimitRemainingThreshold && moment().isBefore(resetTime)) {
    throw {statusCode: 504, error: "Rate limited"};
  }
  // Prevent Reddit from sanitizing '> < &' to '> < &' in the response
  url += (url.indexOf('?') === -1 ? '?' : '&') + 'raw_json=1';
  var headers = {"User-Agent": sails.config.reddit.userAgent};
  if (url.indexOf("oauth.reddit.com") !== -1) {
    headers.Authorization = "bearer " + await exports.refreshToken(refreshToken);
  }
  var options = {
    url: url,
    headers: headers,
    resolveWithFullResponse: true,
    method: requestType,
    formData: data
  };
  let response = await request(options).catch(function (error) {
    if (!silenceErrors) {
      sails.log.error('Reddit error: ' + requestType + ' request sent to ' + url + ' returned ' + error.statusCode);
      sails.log.error('Form data sent: ' + JSON.stringify(data));
    }
    throw {statusCode: error.statusCode, error: '(Reddit response)'};
  });
  updateRateLimits(response);
  var bodyJson;
  try {
    bodyJson = JSON.parse(response.body);
  } catch (error) {
    sails.log.error("Error with parsing: " + response.body);
    throw {error: "Error with parsing: " + response.body};
  }
  return bodyJson;
};

var getEntireListing = async function (refreshToken, endpoint, query, rateThreshold, after, before) {
  var url = endpoint + query + (query ? '&' : '?') + 'count=102&limit=100' + (after ? '&after=' + after : '') + (before ? '&before=' + before : '');
  var batch = await makeRequest(refreshToken, 'GET', url, undefined, rateThreshold, after, before);
  var results = batch.data.children;
  after = before ? undefined : batch.data.after;
  before = before ? batch.data.before : undefined;
  if (!after && !before) {
    return results;
  }
  return _.union(results, await getEntireListing(refreshToken, endpoint, query, rateThreshold, after, before));
};

exports.getFlair = async function (refreshToken, user, subreddit) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flairselector';
  var data = {name: user};
  //Return a Promise containing `response.current` (the flair itself) instead of `response` (the object which contains the flair).
  let res = await makeRequest(refreshToken, 'POST', url, data, 20);
  return res.current;
};

exports.getBothFlairs = async function (refreshToken, user) {
  var ptradesFlairPromise = exports.getFlair(refreshToken, user, 'pokemontrades');
  var svexFlairPromise = exports.getFlair(refreshToken, user, 'SVExchange');
  return Promise.all([ptradesFlairPromise, svexFlairPromise]);
};

exports.setUserFlair = function (refreshToken, name, cssClass, text, subreddit) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/flair';
  var data = {api_type: 'json', css_class: cssClass, name: name, text: text};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.setLinkFlair = function (refreshToken, subreddit, link_id, cssClass, text) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/api/flair';
  var data = {api_type: 'json', css_class: cssClass, link: 't3_' + link_id, text: text};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.checkUsernameAvailable = async function (name) {
  return makeRequest(undefined, 'GET', 'https://www.reddit.com/api/username_available.json?user=' + name, undefined, 10);
};

exports.banUser = function (refreshToken, username, ban_message, note, subreddit, duration) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/friend';
  var data = {api_type: 'json', ban_message: ban_message, duration: (duration ? duration : 'undefined'), name: username, note: note, type: 'banned'};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.getWikiPage = async function (refreshToken, subreddit, page) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/wiki/' + page;
  //Return a Promise for content of the page instead of all the other data
  let res = await makeRequest(refreshToken, 'GET', url, undefined, 5);
  return res.data.content_md;
};

exports.editWikiPage = function (refreshToken, subreddit, page, content, reason) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : subreddit;
  var url = 'https://oauth.reddit.com/r/' + actual_sub + '/api/wiki/edit';
  var data = {content: content, page: page, reason: reason};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.searchTSVThreads = function (refreshToken, username) {
  var actual_sub = sails.config.debug.reddit ? sails.config.debug.subreddit : 'SVExchange';
  var query = "flair:tsv AND author:" + username;
  return exports.search(refreshToken, actual_sub, query, true, 'new', 'all', 'lucene');
};

exports.search = function (refreshToken, subreddit, query, restrict_sr, sort, time, syntax) {
  var querystring = '?q=' + encodeURIComponent(query) + (restrict_sr  ? '&restrict_sr=on' : '') +
    (sort ? '&sort=' + sort : '') + (time ? '&t=' + time : '') + '&syntax=' + (syntax ? syntax : 'lucene');
  var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/search';
  return getEntireListing(refreshToken, endpoint, querystring, 10);
};

exports.removePost = function (refreshToken, id, isSpam) {
  var url = 'https://oauth.reddit.com/api/remove';
  var data = {id: 't3_' + id, spam: isSpam};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.lockPost = function (refreshToken, post_id) {
  var url = 'https://oauth.reddit.com/api/lock';
  var data = {id: 't3_' + post_id};
  /* Attempting to lock an archived post results in a 400 response. The request is still considered successful if this happens, so the error is
   * returned instead of being thrown. */
  return makeRequest(refreshToken, 'POST', url, data, 5, true).catch(function (error) {
    if (error.statusCode === 400) {
      return error;
    }
    throw error;
  });
};

exports.markNsfw = function (refreshToken, post_id) {
  var url = 'https://oauth.reddit.com/api/marknsfw';
  var data = {id: 't3_' + post_id};
  return makeRequest(refreshToken, 'POST', url, data, 5);
};

exports.sendPrivateMessage = function (refreshToken, subject, text, recipient) {
  var url = 'https://oauth.reddit.com/api/compose';
  var data = {api_type: 'json', subject: subject, text: text, to: recipient};
  return makeRequest(refreshToken, 'POST', url, data, 25);
};

exports.sendReply = function (refreshToken, text, parent_id) {
  var url = 'https://oauth.reddit.com/api/comment';
  var data = {api_type: 'json', text: text, thing_id: parent_id};
  return makeRequest(refreshToken, 'POST', url, data, 30);
};

exports.getModeratorPermissions = async function (refreshToken, username, subreddit) {
  var url = 'https://oauth.reddit.com/r/' + subreddit + '/about/moderators?user=' + username;
  let res = await makeRequest(refreshToken, 'GET', url, undefined, 5);
  return res.data.children.length ? res.data.children.find(child => child.name === username).mod_permissions : null;
};

exports.getModmail = async function (refreshToken, subreddit, after, before) {
  var endpoint = 'https://oauth.reddit.com/r/' + subreddit + '/message/moderator';
  return getEntireListing(refreshToken, endpoint, '', 20, after, before);
};

var updateRateLimits = function (res) {
  if (res && res.headers && res.headers['x-ratelimit-remaining'] && res.headers['x-ratelimit-reset']) {
    left = res.headers['x-ratelimit-remaining'];
    resetTime = moment().add(res.headers['x-ratelimit-reset'], "seconds");
  }
};
<|endoftext|>


<commit_before>/**
  Copyright 2011 Christian Iversen <ci@sikkerhed.org>

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
**/

function sprintf(obj, args) {

    var get_argument = function() {
        if (flag_name) {
            return args.PY$__getitem__(flag_name);
        } else {
            return args.PY$__getitem__(argc++);
        }
    };
    var fixed_digits = function(num, digits) {
        if (digits > 0 && digits < num.length) {
            if (num[digits] >= "5") {
                return num.substring(0, digits-1) + (parseInt(num[digits-1]) + 1).toString();
            } else {
                return num.substring(0, digits);
            }
        } else {
            return num;
        }
    };
    var format_float = function(num, defprec, prec) {
        if (prec < 1) {
            prec = defprec;
        }
        var parts = num.toFixed(prec+1).split(".");
        if (prec > defprec) {
            return parts[0] + "." + parts[1].substring(0, prec);
        } else {
            return parts[0] + "." + fixed_digits(parts[1], prec);
        }
    };
    var format_exp = function(num, expchar, defprec, prec, drop_empty_exp) {
        var parts = num.toExponential(defprec).split("e");
        if (parts[1].length < 3) {
            parts[1] = parts[1][0] + "0" + parts[1].substring(1);
        }
        if (prec) {
            var decparts = parts[0].split(".");
            decparts[1] = fixed_digits(decparts[1], prec);
            parts[0] = decparts[0] + "." + decparts[1];
        }
        if (drop_empty_exp && parts[1] == "+00") {
            return parts[0];
        } else {
            return parts[0] + expchar + parts[1];
        }
    };

    var s = js(obj);
    var i = 0;
    var res = "";
    var argc = 0;
    if (py_builtins.isinstance(args, tuple([dict, list, tuple])) == false) {
        args = tuple([args]);
    }
    while (i < s.length) {
        if (s[i] == "%") {
            if (++i == s.length) {
                throw py_builtins.ValueError("Incomplete format");
            }

            var flag_zero  = false;
            var flag_minus = false;
            var flag_hash  = false;
            var flag_plus  = false;
            var flag_space = false;
            var flag_len   = 0;
            var flag_len2  = 0;
            var subres     = null;
            var prefix     = "";
            var has_sign  = false;
            var flag_name  = null;

            while (i < s.length) {
                if (s[i] == "0") {
                    flag_zero = true;
                } else if (s[i] == "-") {
                    flag_minus = true;
                } else if (s[i] == "#") {
                    flag_hash = true;
                } else if (s[i] == "0") {
                    flag_zero = true;
                } else if (s[i] == " ") {
                    flag_space = true;
                } else if (s[i] == "+") {
                    flag_space = false;
                    flag_plus  = true;
                } else if (s[i] > "0" && s[i] <= "9") {
                    flag_len = "";
                    while (s[i] >= "0" && s[i] <= "9") {
                        flag_len += s[i];
                        i++;
                    }
                    flag_len = Number(flag_len);
                    i--;
                } else if (s[i] == ".") {
                    i++;
                    while (s[i] >= "0" && s[i] <= "9") {
                        flag_len2 += s[i];
                        i++;
                    }
                    flag_len2 = Number(flag_len2);
                    i--;
                } else if (s[i] == "(") {
                    flag_name = "";
                    while (i++ < s.length) {
                        if (s[i] == ")") {
                            break;
                        }
                        flag_name += s[i];
                    }
                } else if (s[i] == "%") {
                    subres = "%";
                } else if (s[i] == "d" || s[i] == "i" || s[i] == "u") {
                    has_sign = true;
                    subres = js(int(get_argument())).toString();
                } else if (s[i] == "s") {
                    subres = js(get_argument().PY$__str__());
                } else if (s[i] == "r") {
                    subres = js(get_argument().PY$__repr__());
                } else if (s[i] == "f" || s[i] == "F") {
                    has_sign = true;
                    subres = format_float(js(get_argument().PY$__float__()), 6, flag_len2);
                } else if (s[i] == "e" || s[i] == "E") {
                    has_sign = true;
                    var expchar = s[i];
                    subres = format_exp(js(get_argument().PY$__float__()), expchar, 6, flag_len2, false);
                } else if (s[i] == "g" || s[i] == "G") {
                    has_sign = true;
                    var arg = js(get_argument().PY$__float__());

                    var val = flag_len2;
                    if (val == 0)
                        val = 6;

                    if (arg === 0 && !flag_hash) {
                        subres = "0";
                    } else if (arg < 0.0001 || arg.toFixed().split(".")[0].length > val) {
                        expchar = "e";
                        if (s[i] == "G")
                            expchar = "E";
                        subres = format_exp(arg, expchar, 5, flag_len2-1, true);

                        if (!flag_hash) {
                            var parts = subres.split(expchar);
                            while (parts[0].charAt(parts[0].length - 1) == '0') {
                                parts[0] = parts[0].substr(0, parts[0].length - 1);
                            }
                            if (parts[0].charAt(parts[0].length - 1) == '.') {
                                parts[0] = parts[0].substr(0, parts[0].length - 1);
                            }
                            subres = parts[0];
                            if (parts[1]) {
                                subres += expchar + parts[1];
                            }
                        }
                    } else {
                        subres = format_float(arg, 5, flag_len2-1);
                        if (!flag_hash) {
                            while (subres.charAt(subres.length - 1) == '0') {
                                subres = subres.substr(0, subres.length - 1);
                            }
                            if (subres.charAt(subres.length - 1) == '.') {
                                subres = subres.substr(0, subres.length - 1);
                            }
                        }
                    }
                } else if (s[i] == "o") {
                    has_sign = true;
                    subres = js(get_argument().PY$__int__()).toString(8);
                    if (flag_hash && subres[0] !== "0")
                        prefix = "0";
                } else if (s[i] == "x") {
                    has_sign = true;
                    if (flag_hash)
                        prefix = "0x";
                    subres = js(get_argument().PY$__int__()).toString(16);
                } else if (s[i] == "X") {
                    has_sign = true;
                    if (flag_hash)
                        prefix = "0X";
                    subres = js(get_argument().PY$__int__()).toString(16).toUpperCase();
                } else {
                    throw py_builtins.ValueError("Unsupported format character '" + s[i] + "' at index " + String(i));
                }
                i++;
                if (subres != null)
                    break;
            }
            var pad_char = " ";
            if (flag_zero)
                pad_char = "0";
            var sign = "";
            if (has_sign) {
                if (subres[0] == "-" || subres[0] == "+") {
                    sign = subres[0];
                    subres = subres.substring(1);
                } else if (flag_plus) {
                    sign = "+";
                }
            }
            if (flag_space) {
                prefix = " " + prefix;
            }
            if (pad_char == " x") {
                prefix = sign + prefix;
                sign = "";
            }
            var pad = "";
            for (var c = sign.length + prefix.length + subres.length; c < flag_len; c++)
                pad = pad + pad_char;
            if (flag_minus) {
                res += sign + prefix + subres + pad;
            } else {
                if (flag_zero) {
                    res += sign + pad + prefix + subres;
                } else {
                    res += pad + sign + prefix + subres;
                }
            }
         } else {
            res += s[i++];
        }
    }
    return res;
}
<commit_msg> * Simplify sprintf() by creating function to trim trailing zeroes<commit_after>/**
  Copyright 2011 Christian Iversen <ci@sikkerhed.org>

  Permission is hereby granted, free of charge, to any person
  obtaining a copy of this software and associated documentation
  files (the "Software"), to deal in the Software without
  restriction, including without limitation the rights to use,
  copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.
**/

function sprintf(obj, args) {

    var get_argument = function() {
        if (flag_name) {
            return args.PY$__getitem__(flag_name);
        } else {
            return args.PY$__getitem__(argc++);
        }
    };
    var fixed_digits = function(num, digits) {
        if (digits > 0 && digits < num.length) {
            if (num[digits] >= "5") {
                return num.substring(0, digits-1) + (parseInt(num[digits-1]) + 1).toString();
            } else {
                return num.substring(0, digits);
            }
        } else {
            return num;
        }
    };
    var format_float = function(num, defprec, prec) {
        if (prec < 1) {
            prec = defprec;
        }
        var parts = num.toFixed(prec+1).split(".");
        if (prec > defprec) {
            return parts[0] + "." + parts[1].substring(0, prec);
        } else {
            return parts[0] + "." + fixed_digits(parts[1], prec);
        }
    };
    var format_exp = function(num, expchar, defprec, prec, drop_empty_exp) {
        var parts = num.toExponential(defprec).split("e");
        if (parts[1].length < 3) {
            parts[1] = parts[1][0] + "0" + parts[1].substring(1);
        }
        if (prec) {
            var decparts = parts[0].split(".");
            decparts[1] = fixed_digits(decparts[1], prec);
            parts[0] = decparts[0] + "." + decparts[1];
        }
        if (drop_empty_exp && parts[1] == "+00") {
            return parts[0];
        } else {
            return parts[0] + expchar + parts[1];
        }
    };
    var trim_trailing_zeroes = function(s) {
        var i = s.length - 1;
        while (s.charAt(i) == '0') {
            i--;
        }
        s = s.substr(0, i + 1);
        if (s.charAt(s.length - 1) == '.') {
            s = s.substr(0, s.length - 1);
        }
        return s;
    };

    var s = js(obj);
    var i = 0;
    var res = "";
    var argc = 0;
    if (py_builtins.isinstance(args, tuple([dict, list, tuple])) == false) {
        args = tuple([args]);
    }
    while (i < s.length) {
        if (s[i] == "%") {
            if (++i == s.length) {
                throw py_builtins.ValueError("Incomplete format");
            }

            var flag_zero  = false;
            var flag_minus = false;
            var flag_hash  = false;
            var flag_plus  = false;
            var flag_space = false;
            var flag_len   = 0;
            var flag_len2  = 0;
            var subres     = null;
            var prefix     = "";
            var has_sign  = false;
            var flag_name  = null;

            while (i < s.length) {
                if (s[i] == "0") {
                    flag_zero = true;
                } else if (s[i] == "-") {
                    flag_minus = true;
                } else if (s[i] == "#") {
                    flag_hash = true;
                } else if (s[i] == "0") {
                    flag_zero = true;
                } else if (s[i] == " ") {
                    flag_space = true;
                } else if (s[i] == "+") {
                    flag_space = false;
                    flag_plus  = true;
                } else if (s[i] > "0" && s[i] <= "9") {
                    flag_len = "";
                    while (s[i] >= "0" && s[i] <= "9") {
                        flag_len += s[i];
                        i++;
                    }
                    flag_len = Number(flag_len);
                    i--;
                } else if (s[i] == ".") {
                    i++;
                    while (s[i] >= "0" && s[i] <= "9") {
                        flag_len2 += s[i];
                        i++;
                    }
                    flag_len2 = Number(flag_len2);
                    i--;
                } else if (s[i] == "(") {
                    flag_name = "";
                    while (i++ < s.length) {
                        if (s[i] == ")") {
                            break;
                        }
                        flag_name += s[i];
                    }
                } else if (s[i] == "%") {
                    subres = "%";
                } else if (s[i] == "d" || s[i] == "i" || s[i] == "u") {
                    has_sign = true;
                    subres = js(int(get_argument())).toString();
                } else if (s[i] == "s") {
                    subres = js(get_argument().PY$__str__());
                } else if (s[i] == "r") {
                    subres = js(get_argument().PY$__repr__());
                } else if (s[i] == "f" || s[i] == "F") {
                    has_sign = true;
                    subres = format_float(js(get_argument().PY$__float__()), 6, flag_len2);
                } else if (s[i] == "e" || s[i] == "E") {
                    has_sign = true;
                    var expchar = s[i];
                    subres = format_exp(js(get_argument().PY$__float__()), expchar, 6, flag_len2, false);
                } else if (s[i] == "g" || s[i] == "G") {
                    has_sign = true;
                    var arg = js(get_argument().PY$__float__());

                    var val = flag_len2;
                    if (val == 0)
                        val = 6;

                    if (arg === 0 && !flag_hash) {
                        subres = "0";
                    } else if (arg < 0.0001 || arg.toFixed().split(".")[0].length > val) {
                        expchar = "e";
                        if (s[i] == "G")
                            expchar = "E";
                        subres = format_exp(arg, expchar, 5, flag_len2-1, true);

                        if (!flag_hash) {
                            var parts = subres.split(expchar);
                            subres = trim_trailing_zeroes(parts[0]);
                            if (parts[1]) {
                                subres += expchar + parts[1];
                            }
                        }
                    } else {
                        subres = format_float(arg, 5, flag_len2-1);
                        if (!flag_hash) {
                            subres = trim_trailing_zeroes(subres);
                        }
                    }
                } else if (s[i] == "o") {
                    has_sign = true;
                    subres = js(get_argument().PY$__int__()).toString(8);
                    if (flag_hash && subres[0] !== "0")
                        prefix = "0";
                } else if (s[i] == "x") {
                    has_sign = true;
                    if (flag_hash)
                        prefix = "0x";
                    subres = js(get_argument().PY$__int__()).toString(16);
                } else if (s[i] == "X") {
                    has_sign = true;
                    if (flag_hash)
                        prefix = "0X";
                    subres = js(get_argument().PY$__int__()).toString(16).toUpperCase();
                } else {
                    throw py_builtins.ValueError("Unsupported format character '" + s[i] + "' at index " + String(i));
                }
                i++;
                if (subres != null)
                    break;
            }
            var pad_char = " ";
            if (flag_zero)
                pad_char = "0";
            var sign = "";
            if (has_sign) {
                if (subres[0] == "-" || subres[0] == "+") {
                    sign = subres[0];
                    subres = subres.substring(1);
                } else if (flag_plus) {
                    sign = "+";
                }
            }
            if (flag_space) {
                prefix = " " + prefix;
            }
            if (pad_char == " x") {
                prefix = sign + prefix;
                sign = "";
            }
            var pad = "";
            for (var c = sign.length + prefix.length + subres.length; c < flag_len; c++)
                pad = pad + pad_char;
            if (flag_minus) {
                res += sign + prefix + subres + pad;
            } else {
                if (flag_zero) {
                    res += sign + pad + prefix + subres;
                } else {
                    res += pad + sign + prefix + subres;
                }
            }
         } else {
            res += s[i++];
        }
    }
    return res;
}
<|endoftext|>


<commit_before>function loadUsers(callback) {
    $('#content').load(`/_users.html`, callback);
}<commit_msg>load user list + handle users order #62.<commit_after>function loadUserList(apiRoute, page, url) {
    showLoader();
    $('#users_order_dropdown').dropdown({
        onChange: function (value, text) { handleUsersOrder(value, page); }
    });
    $.get(apiRoute)
        .done(function (result) {
            printUserItems(result.data);
            let paginator = $('#user_paginator');
            printPaginator(paginator, page, result.last_page, url);
            hideLoader();
        })
        .fail(function (error) {
            $('#user_list').html('No se pueden obtener los repositorios en este momento.');
            hideLoader();
        });
}

function handleUsersOrder(value, page) {
    switch (value) {
        case 'date_asc':
            app.setLocation(`#/users/order/date/asc/page/${page}`);
            break;
        case 'date_desc':
            app.setLocation(`#/users/order/date/desc/page/${page}`);
            break;
        case 'name_asc':
            app.setLocation(`#/users/order/name/asc/page/${page}`);
            break;
        case 'name_desc':
            app.setLocation(`#/users/order/name/desc/page/${page}`);
            break;
        case 'pulls_asc':
            app.setLocation(`#/users/order/pullrequests/asc/page/${page}`);
            break;
        case 'pulls_desc':
            app.setLocation(`#/users/order/pullrequests/desc/page/${page}`);
            break;
        case 'reviews_asc':
            app.setLocation(`#/users/order/reviews/asc/page/${page}`);
            break;
        case 'reviews_desc':
            app.setLocation(`#/users/order/reviews/desc/page/${page}`);
            break;
        case 'accepted_reviews_asc':
            app.setLocation(`#/users/order/reviews/accepted/asc/page/${page}`);
            break;
        case 'accepted_reviews_desc':
            app.setLocation(`#/users/order/reviews/accepted/desc/page/${page}`);
            break;
        case 'commented_reviews_asc':
            app.setLocation(`#/users/order/reviews/commented/asc/page/${page}`);
            break;
        case 'commented_reviews_desc':
            app.setLocation(`#/users/order/reviews/commented/desc/page/${page}`);
            break;
        case 'changes_requested_reviews_asc':
            app.setLocation(`#/users/order/reviews/changes_requested/asc/page/${page}`);
            break;
        case 'changes_requested_reviews_desc':
            app.setLocation(`#/users/order/reviews/changes_requested/desc/page/${page}`);
            break;
        case 'dismissed_reviews_asc':
            app.setLocation(`#/users/order/reviews/dismissed/asc/page/${page}`);
            break;
        case 'dismissed_reviews_desc':
            app.setLocation(`#/users/order/reviews/dismissed/desc/page/${page}`);
            break;
    }
}<|endoftext|>