<commit_before>'use strict';

var express = require('express');
var router = express.Router();
var util = require('util');
var request = require('request');
var lxc = require('../lxc');
var doapi = require('../doapi')();

var workerSnapID = 'V1'

// console.log = function(){};

var label2runner = {};
var workers = [];
var isCheckingWorkers = false;

var dopletNewID = 0;
var newWorker = {};

var checkDroplet = function(id, time){
	time = time || 5000;
	doapi.dropletInfo(id, function(data){
		newWorker = JSON.parse(data)['droplet'];
		if(newWorker.status == 'active'){
			console.log('Droplet is now active, starting runners in 20 seconds')
			setTimeout(function(){
				console.log('Ready to start runners!')
				startRunners(workers[workers.push(makeWorkerObj(newWorker))-1])
				isCheckingWorkers = false;
			}, 20000);
			return true;
		}else{
			console.log('Worker not ready, check again in ', time, 'MS');
			setTimeout(function(){
				checkDroplet(id)
			}, time);
		}
	});
};

var workerCreate = function(){
	doapi.dropletCreate({
		name: 'clw'+workerSnapID+'-'+(Math.random()*100).toString().replace('.',''),
		image: '17575764'
	}, function(data){
		data = JSON.parse(data);
		dopletNewID = data.droplet.id;
		doapi.dropletSetTag('clw'+workerSnapID, data.droplet.id, function(data){
			setTimeout(function(){checkDroplet(dopletNewID)}, 60000);
		});
	});
};

var workerDestroy = function(worker){
	worker = worker || workers.pop();
	doapi.dropletDestroy(worker.id, function(){});
	checkWorkersBalance();
};

var checkWorkersBalance = function(count){
	if(isCheckingWorkers) return false;
	isCheckingWorkers = true;
	var changed = false;
	console.log('checking balance');
	if(workers.length < count){
		console.log('less then 2 workers, starting a droplet');
		return workerCreate();
	}
	if(workers[workers.length-1].usedrunner !== 0){
		console.log('last droplet has no free runners, starting droplet');
		return workerCreate();
	}
	if(workers.length > 2 && workers[workers.length-1].usedrunner === 0 && workers[workers.length-2].usedrunner === 0){
		console.log('Last 2 runners not used, killing last runner');
		workerDestroy();
	}

	for(let worker of workers){
		if(worker.availrunners.length === 0 && worker.usedrunner === 0){
			workerDestroy(worker);
			changed = true;
		}
	}

	console.log('stopping workers balancing check');
	isCheckingWorkers = false;
	if(changed) setTimeout(function(){
		checkWorkersBalance();
	}, 3000);
};

// var start

var ramPercentUsed = function(ip, callback){

	return lxc.exec(
		"python3 -c \"a=`head /proc/meminfo|grep MemAvail|grep -Po '\\d+'`;t=`head /proc/meminfo|grep MemTotal|grep -Po '\\d+'`;print(round(((t-a)/t)*100, 2))\"",
		ip,
		callback
	);
};

var runnerFree = function(runner){
	lxc.stop(runner.name, runner.worker.ip);
	runner.worker.usedrunner--;
	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}
	delete label2runner[runner.label];

	startRunners(runner.worker);
};

var lxcTimeout = function(runner, time){
	time = time || 60000 // 900000; // 15 minutes

	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}

	return runner.timeout = setTimeout(function(){
		runnerFree(runner);
	}, time);
};

var run = function(req, res, runner){

	var httpOptions = {
		url: 'http://' + runner.worker.ip,
		headers: {
			Host: runner.name
		},
		body: JSON.stringify({
			code: req.body.code
		})
	};

	return request.post(httpOptions, function(error, response, body){
		// console.log('runner response:', arguments)
		if(error) return false;
		body = JSON.parse(body);

		body['ip'] = getAvailrunner(runner).label;
		lxcTimeout(runner);
		return res.json(body);

	});
};

var makeWorkerObj = function(worker){
	worker.networks.v4.forEach(function(value){
		worker[value.type+'IP'] = value.ip_address;
	});
	worker.availrunners = [];
	worker.ip = worker.privateIP;
	worker.usedrunner = 0;
	worker.index = workers.length,
	worker.getRunner = function(){
		if(this.availrunners.length === 0) return false;
		console.log('geting runner from ', worker.name, ' aval length ', this.availrunners.length);
		var runner = this.availrunners.pop();
		this.usedrunner++;
		label2runner[runner.label] = runner;

		return runner;
	}
	return worker;
};

var initWorkers = function(){
	doapi.dropletsByTag('clw'+workerSnapID, function(data){
		data = JSON.parse(data);
		data['droplets'].forEach(function(worker){

			doapi.dropletDestroy(worker.id, function(){});
		});
		checkWorkersBalance();
	});
};

var getAvailrunner = function(runner){
	for(let worker of workers){
		console.log('checking ', worker.name, ' with ', worker.availrunners.length, ' free workers');
		if(worker.availrunners.length === 0) continue;
		if(runner && runner.worker.index <= worker.index) break;
		if(runner) runnerFree(runner);
		return worker.getRunner();
	}
	if(runner) return runner;
	return false;
};

var startRunners = function(worker, stopPercent){
	console.log('starting runners on', worker.name)
	stopPercent = stopPercent || 80;
	ramPercentUsed(worker.ip, function(usedMemPercent){
		if(usedMemPercent < stopPercent ){
			var name = 'crunner-'+(Math.random()*100).toString().replace('.','');
			return lxc.startEphemeral(name, 'crunner0', worker.ip, function(data){
				if( !data.ip ) return setTimeout(startRunners(worker),0);
				console.log('started runner')

				worker.availrunners.push({
					ip: data.ip,
					name: name,
					worker: worker,
					label: worker.name + ':' + name
				});
				return setTimeout(startRunners(worker, stopPercent), 0);
			});
		}else{
			setTimeout(checkWorkersBalance, 10000);
			console.log('using', usedMemPercent, 'percent memory, stopping runner creation!', worker.availrunners.length, 'created on ', worker.name);
		}
	});
};

initWorkers();

// router.get('/start/:name', function(req, res, next){
// 	return lxc.start(req.params.name, function(data){
// 		if(!data){
// 			return res.json({
// 				status: 500,
// 				name: req.params.name,
// 				message: data
// 			});
// 		}else{
// 			res.json({});
// 		}
// 	});
// });

// router.get('/live/:template/:name', function(req, res, next){
// 	return lxc.startEphemeral(req.params.name, req.params.template, function (data) {
// 		console.log('live', arguments);
// 		return res.json(data);
// 	});
// });

// router.get('/clone/:template/:name', function(req, res, next){
// 	return lxc.clone(req.params.name, req.params.template, function(data){
// 		console.log('clone', arguments);
// 		if( data.match(/Created runner/) ){
// 			return res.json({status: 200});
// 		}else{
// 			return res.json({status: 500, message: data});
// 		}
// 	});
// });

// router.get('/destroy/:name', function(req, res, next){
// 	return lxc.destroy(req.params.name, function(data){
// 		console.log('destroy', arguments);
// 		if(data){
// 			return res.json({status: 500, message: data});
// 		}else{
// 			return res.json({status: 200});
// 		}
// 	});
// });

// router.get('/info/:name', function(req, res, next){
// 	return lxc.info(req.params.name, function(data){
// 		return res.json(data);
// 	});
// });

// router.get('/list', function(req, res, next) {
// 	return lxc.list(workers.clworker0.ip, function(data){
// 		return res.json(data);
// 	});
// });

router.get('/stop/:name', function(req, res, next){
	return lxc.stop(req.params.name, function(data){
		console.log('stop', arguments);
		if(data){
			return res.json({status: 500, name: req.params.name, message: data});
		}else{
			return res.json({status: 200});
		}
	});
});

router.get('/liststuff', function(req, res, next){
	var obj = util.inspect(workers, {depth: 4});
	res.send(obj);
});

router.post('/run/:ip?', function doRun(req, res, next){
	console.log('hit runner route')
	var runner = label2runner[req.params.ip] || getAvailrunner();
	console.log('')
	return run(req, res, runner);
});

module.exports = router;
<commit_msg>stuff<commit_after>'use strict';

var express = require('express');
var router = express.Router();
var util = require('util');
var request = require('request');
var lxc = require('../lxc');
var doapi = require('../doapi')();

var workerSnapID = 'V1'

// console.log = function(){};

var label2runner = {};
var workers = [];
var isCheckingWorkers = false;

var dopletNewID = 0;
var newWorker = {};

var checkDroplet = function(id, time){
	time = time || 5000;
	doapi.dropletInfo(id, function(data){
		newWorker = JSON.parse(data)['droplet'];
		if(newWorker.status == 'active'){
			console.log('Droplet is now active, starting runners in 20 seconds')
			setTimeout(function(){
				console.log('Ready to start runners!')
				startRunners(workers[workers.push(makeWorkerObj(newWorker))-1])
				isCheckingWorkers = false;
			}, 20000);
			return true;
		}else{
			console.log('Worker not ready, check again in ', time, 'MS');
			setTimeout(function(){
				checkDroplet(id)
			}, time);
		}
	});
};

var workerCreate = function(){
	doapi.dropletCreate({
		name: 'clw'+workerSnapID+'-'+(Math.random()*100).toString().replace('.',''),
		image: '17575764'
	}, function(data){
		data = JSON.parse(data);
		dopletNewID = data.droplet.id;
		doapi.dropletSetTag('clw'+workerSnapID, data.droplet.id, function(data){
			setTimeout(function(){checkDroplet(dopletNewID)}, 60000);
		});
	});
};

var workerDestroy = function(worker){
	worker = worker || workers.pop();
	doapi.dropletDestroy(worker.id, function(){});
	checkWorkersBalance();
};

var checkWorkersBalance = function(){
	if(isCheckingWorkers) return false;
	isCheckingWorkers = true;
	var changed = false;
	console.log('checking balance');

	if(workers.length < 2){
		console.log('less then 2 workers, starting a droplet');
		return workerCreate();
	}
	if(workers[workers.length-1].usedrunner !== 0){
		console.log('last droplet has no free runners, starting droplet');
		return workerCreate();
	}
	if(workers.length > 2 && workers[workers.length-1].usedrunner === 0 && workers[workers.length-2].usedrunner === 0){
		console.log('Last 2 runners not used, killing last runner');
		workerDestroy();
	}

	for(let worker of workers){
		if(worker.availrunners.length === 0 && worker.usedrunner === 0){
			workerDestroy(worker);
			changed = true;
		}
	}

	console.log('stopping workers balancing check');
	isCheckingWorkers = false;
	if(changed) setTimeout(function(){
		checkWorkersBalance();
	}, 3000);
};

// var start

var ramPercentUsed = function(ip, callback){

	return lxc.exec(
		"python3 -c \"a=`head /proc/meminfo|grep MemAvail|grep -Po '\\d+'`;t=`head /proc/meminfo|grep MemTotal|grep -Po '\\d+'`;print(round(((t-a)/t)*100, 2))\"",
		ip,
		callback
	);
};

var runnerFree = function(runner){
	lxc.stop(runner.name, runner.worker.ip);
	runner.worker.usedrunner--;
	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}
	delete label2runner[runner.label];

	startRunners(runner.worker);
};

var lxcTimeout = function(runner, time){
	time = time || 60000 // 900000; // 15 minutes

	if(runner.hasOwnProperty('timeout')){
		clearTimeout(runner.timeout);
	}

	return runner.timeout = setTimeout(function(){
		runnerFree(runner);
	}, time);
};

var run = function(req, res, runner){

	var httpOptions = {
		url: 'http://' + runner.worker.ip,
		headers: {
			Host: runner.name
		},
		body: JSON.stringify({
			code: req.body.code
		})
	};

	return request.post(httpOptions, function(error, response, body){
		// console.log('runner response:', arguments)
		if(error) return false;
		body = JSON.parse(body);

		body['ip'] = getAvailrunner(runner).label;
		lxcTimeout(runner);
		return res.json(body);

	});
};

var makeWorkerObj = function(worker){
	worker.networks.v4.forEach(function(value){
		worker[value.type+'IP'] = value.ip_address;
	});
	worker.availrunners = [];
	worker.ip = worker.privateIP;
	worker.usedrunner = 0;
	worker.index = workers.length,
	worker.getRunner = function(){
		if(this.availrunners.length === 0) return false;
		console.log('geting runner from ', worker.name, ' aval length ', this.availrunners.length);
		var runner = this.availrunners.pop();
		this.usedrunner++;
		label2runner[runner.label] = runner;

		return runner;
	}
	return worker;
};

var initWorkers = function(){
	doapi.dropletsByTag('clw'+workerSnapID, function(data){
		data = JSON.parse(data);
		data['droplets'].forEach(function(worker){

			doapi.dropletDestroy(worker.id, function(){});
		});
		checkWorkersBalance();
	});
};

var getAvailrunner = function(runner){
	for(let worker of workers){
		console.log('checking ', worker.name, ' with ', worker.availrunners.length, ' free workers');
		if(worker.availrunners.length === 0) continue;
		if(runner && runner.worker.index <= worker.index) break;
		if(runner) runnerFree(runner);
		return worker.getRunner();
	}
	if(runner) return runner;
	return false;
};

var startRunners = function(worker, stopPercent){
	console.log('starting runners on', worker.name)
	stopPercent = stopPercent || 80;
	ramPercentUsed(worker.ip, function(usedMemPercent){
		if(usedMemPercent < stopPercent ){
			var name = 'crunner-'+(Math.random()*100).toString().replace('.','');
			return lxc.startEphemeral(name, 'crunner0', worker.ip, function(data){
				if( !data.ip ) return setTimeout(startRunners(worker),0);
				console.log('started runner')

				worker.availrunners.push({
					ip: data.ip,
					name: name,
					worker: worker,
					label: worker.name + ':' + name
				});
				return setTimeout(startRunners(worker, stopPercent), 0);
			});
		}else{
			setTimeout(checkWorkersBalance, 10000);
			console.log('using', usedMemPercent, 'percent memory, stopping runner creation!', worker.availrunners.length, 'created on ', worker.name);
		}
	});
};

initWorkers();

// router.get('/start/:name', function(req, res, next){
// 	return lxc.start(req.params.name, function(data){
// 		if(!data){
// 			return res.json({
// 				status: 500,
// 				name: req.params.name,
// 				message: data
// 			});
// 		}else{
// 			res.json({});
// 		}
// 	});
// });

// router.get('/live/:template/:name', function(req, res, next){
// 	return lxc.startEphemeral(req.params.name, req.params.template, function (data) {
// 		console.log('live', arguments);
// 		return res.json(data);
// 	});
// });

// router.get('/clone/:template/:name', function(req, res, next){
// 	return lxc.clone(req.params.name, req.params.template, function(data){
// 		console.log('clone', arguments);
// 		if( data.match(/Created runner/) ){
// 			return res.json({status: 200});
// 		}else{
// 			return res.json({status: 500, message: data});
// 		}
// 	});
// });

// router.get('/destroy/:name', function(req, res, next){
// 	return lxc.destroy(req.params.name, function(data){
// 		console.log('destroy', arguments);
// 		if(data){
// 			return res.json({status: 500, message: data});
// 		}else{
// 			return res.json({status: 200});
// 		}
// 	});
// });

// router.get('/info/:name', function(req, res, next){
// 	return lxc.info(req.params.name, function(data){
// 		return res.json(data);
// 	});
// });

// router.get('/list', function(req, res, next) {
// 	return lxc.list(workers.clworker0.ip, function(data){
// 		return res.json(data);
// 	});
// });

router.get('/stop/:name', function(req, res, next){
	return lxc.stop(req.params.name, function(data){
		console.log('stop', arguments);
		if(data){
			return res.json({status: 500, name: req.params.name, message: data});
		}else{
			return res.json({status: 200});
		}
	});
});

router.get('/liststuff', function(req, res, next){
	var obj = util.inspect(workers, {depth: 4});
	res.send(obj);
});

router.post('/run/:ip?', function doRun(req, res, next){
	console.log('hit runner route')
	var runner = label2runner[req.params.ip] || getAvailrunner();
	console.log('')
	return run(req, res, runner);
});

module.exports = router;
<|endoftext|>


<commit_before>document.addEventListener('WebComponentsReady', function() {

        var ldfClientStreaming = document.querySelector('#ldf-client-streaming');
        // Process data as it appears
        ldfClientStreaming.addEventListener('ldf-query-streaming-response-partial',
            function(e) {
          var pre = document.createElement('pre');
          pre.classList.add('streaming');
          pre.textContent = JSON.stringify(e.detail.response);
          document.querySelector('#td2').appendChild(pre);
        });
        // Get notified once all data is received
        ldfClientStreaming.addEventListener('ldf-query-streaming-response-end', function() {
          alert('Received all data');
        });
      });<commit_msg>Filter test<commit_after>document.addEventListener('WebComponentsReady', function() {

        var ldfClientStreaming = document.querySelector('#ldf-client-streaming');
        // Process data as it appears
        ldfClientStreaming.addEventListener('ldf-query-streaming-response-partial',
            function(e) {
          var pre = document.createElement('pre');
          pre.classList.add('streaming');
          pre.textContent = JSON.stringify(e.detail.response);
          document.querySelector('#td2').appendChild(pre);
        });
        // Get notified once all data is received
        ldfClientStreaming.addEventListener('ldf-query-streaming-response-end', function() {
          alert('Received all data');
        });
      });

var array = [];
array.push({ term: {'sentiment': 'negative'}})
console.log(array)

var client = new $.es.Client({
          hosts: 'localhost:9200'
        });

      var id = this.extraId;
        client.search({
          // undocumented params are appended to the query string
          index: 'sentiment',
          type: 'tweet',
          body: {
            size: 10,
            query: {
              filtered: {
                filter: {
                  bool: {
                    should: array,
                  }
                }
              }
            },
            aggs: {
              rest: {
                terms: {
                  field: 'sentiment',
                  size: 5,
                  order: {
                    _count: "desc"
                  }
                }
              }
            }
          }
        }).then(function (resp) {
          console.log(resp)
        });<|endoftext|>


<commit_before>var express = require('express');
_ = require('underscore')._
var request = require('request');
var router = express.Router({mergeParams: true});
var exec = require('child_process').exec;
var path = require('path');
var parentDir = path.resolve(process.cwd());

router.get('/', function(req, res, next) {
    res.json({'info' : 'This is the ControCurator API v0.1'});
});

router.post('/',function(req,res) {
    //try {
      //  var input = JSON.parse(req);
    //} catch (e) {
    //    res.json({'error':'Input was not valid JSON.'});
    //}
    var input = req;
    var amountofitems = input.length;
    var gooditems = 0;
    var baditems = 0;

    for (var i=0; i < amountofitems; i++)
    {
        var currentitem = input[i];
        if (!currentitem.hasOwnProperty('id') || !currentitem.hasOwnProperty('text'))
        {
            baditems++;
            continue;
        }
        gooditems++;
    }
    var controscore = Math.random();

    res.json({'controversy':controscore,'totalItems':amountofitems,'goodItems':gooditems,'badItems':baditems});

});
module.exports = router;
<commit_msg>More testing<commit_after>var express = require('express');
_ = require('underscore')._
var request = require('request');
var router = express.Router({mergeParams: true});
var exec = require('child_process').exec;
var path = require('path');
var parentDir = path.resolve(process.cwd());

router.get('/', function(req, res, next) {
    res.json({'info' : 'This is the ControCurator API v0.1'});
});

router.post('/',function(req,res) {
    //try {
      //  var input = JSON.parse(req);
    //} catch (e) {
    //    res.json({'error':'Input was not valid JSON.'});
    //}
    console.log(req);
    var input = req;
    var amountofitems= input.length;
    console.log("total: "+amountofitems);
    var gooditems = 0;
    var baditems = 0;

    for (var i=0; i < amountofitems; i++)
    {
        var currentitem = input[i];
        if (!currentitem.hasOwnProperty('id') || !currentitem.hasOwnProperty('text'))
        {
            baditems++;
            continue;
        }
        gooditems++;
    }
    var controscore = Math.random();

    res.json({'controversy':controscore,'totalItems':amountofitems,'goodItems':gooditems,'badItems':baditems});

});
module.exports = router;
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var models = require('../models');

router.use(function (req, res, next) {
  var page = 1;
  if (req.query.page) {
    page = req.query.page;
  }
  var limit = 20
  var offset = (page - 1) * limit;
  req.limit = limit;
  req.offset = offset;
  next();
});

router.get('/parlamentares', function(req, res, next) {
  // filters
  var where = {}
  if (req.query.ideCadastro) {
    where.ideCadastro = req.query.ideCadastro
  }
  if (req.query.txNomeParlamentar) {
    where.txNomeParlamentar = {
      $iLike: '%' + req.query.txNomeParlamentar + '%'
    }
  }
  if (req.query.nuCarteiraParlamentar) {
    where.nuCarteiraParlamentar = req.query.nuCarteiraParlamentar
  }
  if (req.query.nuLegislatura) {
    where.nuLegislatura = req.query.nuLegislatura
  }
  if (req.query.sgUF) {
    where.sgUF = req.query.sgUF
  }
  if (req.query.sgPartido) {
    where.sgPartido = req.query.sgPartido
  }
  console.log(req.offset);
  console.log(req.limit);
  models.Parlamentar
    .findAndCountAll({
      where: where,
      offset: req.offset,
      limit: req.limit,
      order: [
        ['txNomeParlamentar', 'ASC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/subcotas', function(req, res, next) {
  // filters
  var where = {}
  if (req.query.numSubCota) {
    where.numSubCota = req.query.numSubCota
  }
  if (req.query.txtDescricao) {
    where.txtDescricao = {
      $iLike: '%' + req.query.txtDescricao + '%'
    }
  }

  models.SubCota
    .findAndCountAll({
      where: where,
      include: [
        {model: models.EspecificacaoSubCota},
      ],
      offset: req.offset,
      limit: req.limit,
      order: [
        ['txtDescricao', 'ASC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/despesas', function(req, res, next) {
  // filters
  var where = {}
  if (req.query.txtFornecedor) {
    where.txtFornecedor = {
      $iLike: '%' + req.query.txtFornecedor + '%'
    }
  }
  if (req.query.numAno) {
    where.numAno = req.query.numAno
  }
  if (req.query.numMes) {
    where.numMes = req.query.numMes
  }
  if (req.query.parlamentarId) {
    where.parlamentarId = req.query.parlamentarId
  }
  if (req.query.subCotaId) {
    where.subCotaId = req.query.subCotaId
  }
  if (req.query.especificacaoSubCotaId) {
    where.especificacaoSubCotaId = req.query.especificacaoSubCotaId
  }

  models.Despesa
    .findAndCountAll({
      where: where,
      offset: req.offset,
      limit: req.limit,
      order: [
        ['datEmissao', 'DESC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

module.exports = router;
<commit_msg>Adicionando os detalhes para os endpoints.<commit_after>var express = require('express');
var router = express.Router();
var models = require('../models');

router.use(function(req, res, next) {
  var page = 1;
  if (req.query.page) {
    page = req.query.page;
  }
  var limit = 30
  var offset = (page - 1) * limit;
  req.limit = limit;
  req.offset = offset;
  next();
});

router.get('/parlamentares', function(req, res, next) {

  var where = {}
  if (req.query.ideCadastro) {
    where.ideCadastro = req.query.ideCadastro
  }
  if (req.query.txNomeParlamentar) {
    where.txNomeParlamentar = {
      $iLike: '%' + req.query.txNomeParlamentar + '%'
    }
  }
  if (req.query.nuCarteiraParlamentar) {
    where.nuCarteiraParlamentar = req.query.nuCarteiraParlamentar
  }
  if (req.query.nuLegislatura) {
    where.nuLegislatura = req.query.nuLegislatura
  }
  if (req.query.sgUF) {
    where.sgUF = req.query.sgUF
  }
  if (req.query.sgPartido) {
    where.sgPartido = req.query.sgPartido
  }

  models.Parlamentar
    .findAndCountAll({
      where: where,
      offset: req.offset,
      limit: req.limit,
      order: [
        ['txNomeParlamentar', 'ASC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/parlamentares/:id', function(req, res, next) {

  models.Parlamentar.findById(req.params.id)
    .then(function(object) {
      if (object) {
        res.json(object);
      } else {
        res.status(404).json({
          error: 'Recurso não encontrado'
        });
      }
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/subcotas', function(req, res, next) {

  var where = {}
  if (req.query.numSubCota) {
    where.numSubCota = req.query.numSubCota
  }
  if (req.query.txtDescricao) {
    where.txtDescricao = {
      $iLike: '%' + req.query.txtDescricao + '%'
    }
  }

  models.SubCota
    .findAndCountAll({
      where: where,
      include: [{
        model: models.EspecificacaoSubCota
      }, ],
      offset: req.offset,
      limit: req.limit,
      order: [
        ['txtDescricao', 'ASC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/subcotas/:id', function(req, res, next) {

  models.SubCota
    .findOne({
      where: {
        numSubCota: req.params.id
      },
      include: [{
        model: models.EspecificacaoSubCota
      }, ],
    })
    .then(function(object) {
      if (object) {
        res.json(object);
      } else {
        res.status(404).json({
          error: 'Recurso não encontrado'
        });
      }
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/despesas', function(req, res, next) {

  var where = {}
  if (req.query.txtFornecedor) {
    where.txtFornecedor = {
      $iLike: '%' + req.query.txtFornecedor + '%'
    }
  }
  if (req.query.numAno) {
    where.numAno = req.query.numAno
  }
  if (req.query.numMes) {
    where.numMes = req.query.numMes
  }
  if (req.query.parlamentarId) {
    where.parlamentarId = req.query.parlamentarId
  }
  if (req.query.subCotaId) {
    where.subCotaId = req.query.subCotaId
  }
  if (req.query.especificacaoSubCotaId) {
    where.especificacaoSubCotaId = req.query.especificacaoSubCotaId
  }

  models.Despesa
    .findAndCountAll({
      where: where,
      offset: req.offset,
      limit: req.limit,
      order: [
        ['datEmissao', 'DESC'],
      ]
    })
    .then(function(result) {
      res.json({
        count: result.count,
        pages: Math.ceil(result.count / req.limit),
        data: result.rows
      })
    })
    .catch(function(err) {
      res.send(err);
    });

});

router.get('/despesas/:id', function(req, res, next) {

  models.Despesa.findById(req.params.id)
    .then(function(object) {
      if (object) {
        res.json(object);
      } else {
        res.status(404).json({
          error: 'Recurso não encontrado'
        });
      }
    })
    .catch(function(err) {
      res.send(err);
    });

});

module.exports = router;
<|endoftext|>


<commit_before>import "../loadenv"

import stream from "getstream"
import moment from "moment"
import normalize from "normalize-url"

import RSS from "../models/rss"
import Article from "../models/article"

import "../utils/db"
import config from "../config"
import logger from "../utils/logger"
import util from "util"

import sendRssFeedToCollections from "../utils/events/sendRssFeedToCollections"
import { ParseFeed } from "../parsers"

import async_tasks from "../async_tasks"
import { getStatsDClient } from '../utils/statsd';

const streamClient = stream.connect(config.stream.apiKey, config.stream.apiSecret)

// connect the handler to the queue
logger.info("Starting the RSS worker")

// TODO: move this to a separate main.js
async_tasks.ProcessRssQueue(30, handleRSS)

const statsd = getStatsDClient()

// the top level handleRSS just intercepts error handling before it goes to Bull
async function handleRSS(job) {
    let promise = _handleRSS(job)
    promise.catch(err => {
        logger.warn(`rss job ${job} broke with err ${err}`)
        console.log(err)

    })
    return promise
}

// Handle Podcast scrapes the podcast and updates the episodes
async function _handleRSS(job) {
    logger.info(`Processing ${job.data.url}`)

    // verify we have the rss object
    let rssID = job.data.rss
    let rss = await RSS.findOne({ _id: rssID })
    if (!rss) {
        logger.warn(`RSS with ID ${rssID} does not exist`)
        return
    }

    // mark as done, will be schedule again in 15 min from now
    // we do this early so a temporary failure doesnt leave things in a broken state
    await markDone(rssID)
    logger.info(`Marked ${rssID} as done`)

    // parse the articles
    let rssContent
    try {
        rssContent = await util.promisify(ParseFeed)(job.data.url)
    } catch (e) {
        logger.info(`rss scraping broke for url ${job.data.url}`)
        return
    }

	// update the articles
    logger.info(`Updating ${rssContent.articles.length} articles for feed ${rssID}`)

	if (rssContent.articles.length < 1) {
		return
	}

	statsd.increment("winds.handle_rss.articles.parsed", rssContent.articles.length)
	statsd.timing("winds.handle_rss.articles.parsed", rssContent.articles.length)

    let allArticles = await upsertManyArticles(rssID, rssContent.articles)

    // updatedArticles will contain `null` for all articles that didn't get updated, that we already have in the system.
    let updatedArticles = allArticles.filter(updatedArticle => {
        return updatedArticle
    })

	statsd.increment("winds.handle_rss.articles.upserted", updatedArticles.length)

	await Promise.all(updatedArticles.map(article => {
		async_tasks.OgQueueAdd(
			{
				type: 'rss',
				url: article.url,
			},
			{
				removeOnComplete: true,
				removeOnFail: true,
			},
		)
	}))

    let rssFeed = streamClient.feed("rss", rssID)
    logger.info(`Syncing ${updatedArticles.length} articles to Stream`)
    if (updatedArticles.length > 0) {
        let chunkSize = 100
        for (let i = 0, j = updatedArticles.length; i < j; i += chunkSize) {
            let chunk = updatedArticles.slice(i, i + chunkSize)
            let streamArticles = chunk.map(article => {
                return {
                    actor: article.rss,
                    foreign_id: `articles:${article._id}`,
                    object: article._id,
                    time: article.publicationDate,
                    verb: "rss_article",
                }
            })
            await rssFeed.addActivities(streamArticles)
        }
		await sendRssFeedToCollections(rss)
    }
    logger.info(`Completed scraping for ${job.data.url}`)
}

async function upsertManyArticles(rssID, articles){
	let articlesData = articles.map(article => {
		const clone = Object.assign({}, article)
		clone.url = normalize(article.url)
		if (!clone.images || Object.keys(clone.images).length < 1) {
			delete(clone.images)
		}
		return clone
	})

	let existingArticles = await Article.find({$or: articlesData}, { "url": 1 })
	let existingArticleUrls = existingArticles.map(a => {return a.url})

	statsd.increment("winds.handle_rss.articles.already_in_mongo", existingArticleUrls.length)

	let articlesToUpsert = articlesData.filter(article => {
		return existingArticleUrls.indexOf(article.url) === -1
	})

	logger.info(`Feed ${rssID}: got ${articles.length} articles of which ${articlesToUpsert.length} need a sync`)

	return Promise.all(articlesToUpsert.map(article => {
		return upsertArticle(rssID, article)
	}))
}

// updateArticle updates the article in mongodb if it changed and create a new one if it did not exist
async function upsertArticle(rssID, post) {
	let update = {
		commentUrl: post.commentUrl,
		content: post.content,
		description: post.description,
		publicationDate: post.publicationDate,
		rss: rssID,
		title: post.title,
		url: post.url,
    	enclosures: post.enclosures,
	};

	try {
		return await Article.findOneAndUpdate(
			{
				$and: [
					{
						rss: rssID,
						url: post.url,
					},
					{
						$or: Object.keys(update).map(k => {
							return {
								[k]: {
									$ne: update[k],
								},
							};
						}),
					},
				],
			},
			update,
			{
				new: true,
				upsert: true,
			},
		)
	} catch(err) {
		if (err.code === 11000){
			statsd.increment("winds.handle_rss.articles.ignored")
			return null
		} else {
			throw error;
		}
	}
}

// markDone sets lastScraped to now and isParsing to false
async function markDone(rssID) {
    /*
	Set the last scraped for the given rssID
	*/
    let now = moment().toISOString()
    let updated = await RSS.update(
        { _id: rssID },
        {
            lastScraped: now,
            isParsing: false,
        },
    )
    return updated
}
<commit_msg>split search and update fields to avoid crazy updates<commit_after>import "../loadenv"

import stream from "getstream"
import moment from "moment"
import normalize from "normalize-url"

import RSS from "../models/rss"
import Article from "../models/article"

import "../utils/db"
import config from "../config"
import logger from "../utils/logger"
import util from "util"

import sendRssFeedToCollections from "../utils/events/sendRssFeedToCollections"
import { ParseFeed } from "../parsers"

import async_tasks from "../async_tasks"
import { getStatsDClient } from '../utils/statsd';

const streamClient = stream.connect(config.stream.apiKey, config.stream.apiSecret)

// connect the handler to the queue
logger.info("Starting the RSS worker")

// TODO: move this to a separate main.js
async_tasks.ProcessRssQueue(30, handleRSS)

const statsd = getStatsDClient()

// the top level handleRSS just intercepts error handling before it goes to Bull
async function handleRSS(job) {
    let promise = _handleRSS(job)
    promise.catch(err => {
        logger.warn(`rss job ${job} broke with err ${err}`)
        console.log(err)

    })
    return promise
}

// Handle Podcast scrapes the podcast and updates the episodes
async function _handleRSS(job) {
    logger.info(`Processing ${job.data.url}`)

    // verify we have the rss object
    let rssID = job.data.rss
    let rss = await RSS.findOne({ _id: rssID })
    if (!rss) {
        logger.warn(`RSS with ID ${rssID} does not exist`)
        return
    }

    // mark as done, will be schedule again in 15 min from now
    // we do this early so a temporary failure doesnt leave things in a broken state
    await markDone(rssID)
    logger.info(`Marked ${rssID} as done`)

    // parse the articles
    let rssContent
    try {
        rssContent = await util.promisify(ParseFeed)(job.data.url)
    } catch (e) {
        logger.info(`rss scraping broke for url ${job.data.url}`)
        return
    }

	// update the articles
    logger.info(`Updating ${rssContent.articles.length} articles for feed ${rssID}`)

	if (rssContent.articles.length < 1) {
		return
	}

	statsd.increment("winds.handle_rss.articles.parsed", rssContent.articles.length)
	statsd.timing("winds.handle_rss.articles.parsed", rssContent.articles.length)

    let allArticles = await upsertManyArticles(rssID, rssContent.articles)

    // updatedArticles will contain `null` for all articles that didn't get updated, that we already have in the system.
    let updatedArticles = allArticles.filter(updatedArticle => {
        return updatedArticle
    })

	statsd.increment("winds.handle_rss.articles.upserted", updatedArticles.length)

	await Promise.all(updatedArticles.map(article => {
		async_tasks.OgQueueAdd(
			{
				type: 'rss',
				url: article.url,
			},
			{
				removeOnComplete: true,
				removeOnFail: true,
			},
		)
	}))

    let rssFeed = streamClient.feed("rss", rssID)
    logger.info(`Syncing ${updatedArticles.length} articles to Stream`)
    if (updatedArticles.length > 0) {
        let chunkSize = 100
        for (let i = 0, j = updatedArticles.length; i < j; i += chunkSize) {
            let chunk = updatedArticles.slice(i, i + chunkSize)
            let streamArticles = chunk.map(article => {
                return {
                    actor: article.rss,
                    foreign_id: `articles:${article._id}`,
                    object: article._id,
                    time: article.publicationDate,
                    verb: "rss_article",
                }
            })
            await rssFeed.addActivities(streamArticles)
        }
		await sendRssFeedToCollections(rss)
    }
    logger.info(`Completed scraping for ${job.data.url}`)
}

async function upsertManyArticles(rssID, articles) {
	articles = articles.map(a => {
		a.url = normalize(a.url)
		return a
	})

	let articlesData = articles.map(article => {
		const clone = Object.assign({}, article)
		delete(clone.images)
		delete(clone.enclosures)
		return clone
	})

	let existingArticles = await Article.find({$or: articlesData}, { "url": 1 })
	let existingArticleUrls = existingArticles.map(a => {return a.url})

	statsd.increment("winds.handle_rss.articles.already_in_mongo", existingArticleUrls.length)

	let articlesToUpsert = articlesData.filter(article => {
		return existingArticleUrls.indexOf(article.url) === -1
	})

	logger.info(`Feed ${rssID}: got ${articles.length} articles of which ${articlesToUpsert.length} need a sync`)

	return Promise.all(articlesToUpsert.map(article => {
		return upsertArticle(rssID, article)
	}))
}

// updateArticle updates the article in mongodb if it changed and create a new one if it did not exist
async function upsertArticle(rssID, post) {
	let search = {
		commentUrl: post.commentUrl,
		content: post.content,
		description: post.description,
		publicationDate: post.publicationDate,
		rss: rssID,
		title: post.title,
		url: post.url,
	};

	let update = Object.assign({}, search)
	update.enclosures = post.enclosures
	update.images = post.images

	try {
		return await Article.findOneAndUpdate(
			{
				$and: [
					{
						rss: rssID,
						url: post.url,
					},
					{
						$or: Object.keys(search).map(k => {
							return {
								[k]: {
									$ne: search[k],
								},
							};
						}),
					},
				],
			},
			update,
			{
				new: true,
				upsert: true,
			},
		)
	} catch(err) {
		if (err.code === 11000){
			statsd.increment("winds.handle_rss.articles.ignored")
			return null
		} else {
			throw error;
		}
	}
}

// markDone sets lastScraped to now and isParsing to false
async function markDone(rssID) {
    /*
	Set the last scraped for the given rssID
	*/
    let now = moment().toISOString()
    let updated = await RSS.update(
        { _id: rssID },
        {
            lastScraped: now,
            isParsing: false,
        },
    )
    return updated
}
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc factory
 * @name SubSnoopApp.factory
 * @description
 * # subFactory
 * Factory in the SubSnoopApp.
 */
 angular.module('SubSnoopApp')
 .factory('subFactory', ['$http', 'userFactory', '$q', 'moment', '$sce', '$filter', function ($http, userFactory, $q, moment, $sce, $filter) {
    var baseUrl = 'https://www.reddit.com/user/';
    var rawJson = 'raw_json=1';
    var pages = 10;
    var username;
    var promise;
    var after = "0";
    var subLength = 0;
    var defaultSortedArray = [];

    var comments = [];
    var submissions = [];
    var subs = {};
    var commentData = [];
    var submitData = [];
    var subData = {};

    /*
     Callback function to fetch data from user's comments
    */
    window.commentsCallback = function(response) {
      commentData.push(response.data.children);
      after = response.data.after;
      pushData(response.data, 'comments');
    };

    /*
     Callback function to fetch data from user's submissions
    */
    window.submitsCallback = function(response) {
      submitData.push(response.data.children);
      after = response.data.after;
      pushData(response.data, 'submits');
    };

    /*
     User interface for sub factory
    */
    var factory = {
      getData: function(user) {
        resetData();
        username = user;
        var userPromise = userFactory.getData(user);
        return getSubPromise(userPromise);
      },
      getSubData: function() {
        return subData;
      },
      checkUser: function(user) {
        if (!username) {
          return false;
        } else {
          return matchUser(user, username);
        }
      },
      getDefaultSortedArray: function() {
        return defaultSortedArray;
      },
      getSubLength: function() {
        return subLength;
      }
    };
    return factory;

    /*
     Check to see if two username strings match
    */
    function matchUser(user1, user2) {
      return user1.toLowerCase() === user2.toLowerCase();
    }

    /*
     Configure sub data object, which will be passed to the controllers.
    */
    function setSubData(response) {
      organizeComments(comments);
      organizeSubmitted(submissions);
      setTotalUps();
      setDefaultSortedArray();

      subData = {
        'user': response,
        'comments' : comments.length,
        'submissions' : submissions.length,
        'subs' : subs,
        'firstDate' : getFirstDate(),
        'latest' : getLatest(2)
      }
    };

    /*
     Reset all data to empty lists (used for getting a new user)
    */
    function resetData() {
      after = "0";
      comments = [];
      submissions = [];
      subs = {};
      commentData = [];
      submitData = [];
      subData = {};
    };

    /*
     Only if user promise resolves, then do promise chaining for comments and
     submissions asynchronously
    */
    function getSubPromise(userPromise) {
      var subPromise = userPromise.then(function(response) {
        if (response && response.name.toLowerCase() === username.toLowerCase()) {
          var commentPromise = promiseChain('comments', 'commentsCallback');
          var submitPromise = promiseChain('submitted', 'submitsCallback');

          // Resolve both comment and submission promises together
          var dataPromise = $q.all([commentPromise, submitPromise]).then(function() {
            setSubData(response);
            return subData;
          });
          return dataPromise;
        } else {
          return null;
        }
      });
      return subPromise;
    };

    /*
     Make the http request to the Reddit API using JSONP.
    */
    function getJSONP(where, callback) {
      var url = 'https://www.reddit.com/user/'+username+'/'+where+'.json?limit=100&after='+after+'&jsonp='+callback;
      var trustedUrl =  $sce.trustAsResourceUrl(url);
      return $http.jsonp(trustedUrl);
    };

    /*
     Push the comment/submission data to their respective lists
    */
    function pushData(response, where) {
      if (response) {
        var data = response.children;
        for (var i = 0; i < data.length; i++) {
          var item = data[i].data;
          if (where === 'comments') {
            item.type = 'comment';
            comments.push(item);
          } else {
            item.type = 'submit';
            submissions.push(item);
          }
        }
      }
    };

    function getDataList(where) {
      return where === 'comments' ? commentData : submitData;
    };

    /*
     Resolve promise and return data if there is no more requests.
     If there is still an after value, chain the next promise.
    */
    function getPromise(where, callback, promise, index) {
      var promise = promise.then(function() {
        return getDataList(where);
      }, function() {
        if (index === pages-1) {
          return getDataList(where);
        } else {
          return after ? getJSONP(where, callback) : getDataList(where);
        }
      });
      return promise;
    };

    /*
     Chain data promises for fetching comments or submissions.
     Reddit API caps at 1000 comments and submisions each. Only 100 items can be
     fetched at a time, making for at most 10 API requests.
    */
    function promiseChain(where, callback) {
      var promise = getJSONP(where, callback);
      for (var i = 0; i < pages; i++) {
        promise = getPromise(where, callback, promise, i);
      }
      return promise;
    };

    /*
     Grabs the comments and store them in their respective sub object
     as well as other statistics
    */
    function organizeComments(comments) {
      subs = {};
      // Push comments
      for (var i = 0; i < comments.length; i++) {
        var comment = comments[i];
        var subreddit = comment.subreddit;
        var ups = parseInt(comment.ups);

        if (subreddit in subs) {
          var comment_list = subs[subreddit].comments;
          var date = moment(comment.created_utc*1000);

          subs[subreddit].comment_ups += ups;

          comment_list.push(comment);
          if (date > subs[subreddit].recent_comment) {
            subs[subreddit].recent_comment = date;
          }
        } else {
          subs[subreddit] = {};
          subs[subreddit].comments = new Array(comment);
          subs[subreddit].recent_comment = moment(comment.created_utc*1000);
          subs[subreddit].comment_ups = ups;
          subs[subreddit].submissions = [];
          subs[subreddit].submission_ups = 0;
          subs[subreddit].gilded_comments = 0;
          subs[subreddit].gilded_submissions = 0;
          subs[subreddit].count = 0;
        }

        if (comment.gilded > 0) {
          subs[subreddit].gilded_comments += 1;
        }

        subs[subreddit].count += 1;
        subs[subreddit].recent_activity = subs[subreddit].recent_comment;
      }
    };

    /*
     Grabs submissions and stores them in their respective sub object
    */
    function organizeSubmitted(submissions) {
      for (var i = 0; i < submissions.length; i++) {
        var submission = submissions[i];
        var subreddit = submission.subreddit;
        var ups = parseInt(submission.ups);

        if (subreddit in subs && subs[subreddit].submissions.length > 0) {
          var submission_list = subs[subreddit].submissions;
          var date = moment(submission.created_utc*1000);
          var recent_submission = subs[subreddit].recent_submission;

          subs[subreddit].submission_ups += ups;

          submission_list.push(submission);
          if (date > recent_submission) {
            subs[subreddit].recent_submission = date;
          }
        } else {
          if (!(subreddit in subs)) {
            subs[subreddit] = {};
            subs[subreddit].submissions = [];
            subs[subreddit].comments = [];
            subs[subreddit].comment_ups = 0;
            subs[subreddit].gilded_submissions = 0;
            subs[subreddit].count = 0;
          }
          subs[subreddit].submissions.push(submission);
          subs[subreddit].recent_submission = moment(submission.created_utc*1000);
          subs[subreddit].submission_ups = ups;
        }

        if (submission.gilded > 0) {
          subs[subreddit].gilded_submissions += 1;
        }

        if ('recent_comment' in subs[subreddit]) {
          if (subs[subreddit].recent_submission > subs[subreddit].recent_comment) {
            subs[subreddit].recent_activity = subs[subreddit].recent_submission;
          }
        } else {
          subs[subreddit].recent_activity = subs[subreddit].recent_submission;
        }

        subs[subreddit].count += 1;
      }
    };

    /*
     Get the combined total of comment and submission upvotes
    */
    function setTotalUps() {
      for (var sub in subs) {
        subs[sub].total_ups = subs[sub].comment_ups + subs[sub].submission_ups;
      }
    };

    /*
     Get the date of the first post
    */
    function getFirstDate() {
      var dataAvailable;
      var lastComment, lastSubmit, commentDate, submitDate;

      if (comments.length > 0) {
        lastComment = comments[comments.length-1];
        commentDate = moment(lastComment.created_utc*1000);
      }

      if (submissions.length > 0) {
        lastSubmit = submissions[submissions.length-1];
        submitDate = moment(lastSubmit.created_utc*1000);
      }

      if (commentDate && submitDate) {
        dataAvailable = (commentDate < submitDate) ? commentDate : submitDate;
      } else if (commentDate) {
        dataAvailable = commentDate;
      } else if (submitDate) {
        dataAvailable = submitDate;
      }
      return dataAvailable;
    };

    /*
     Get the most recent posts (whether they be comment or submission)
    */
    function getLatest(num) {
      var latest = [];
      var comment_index = 0;
      var submit_index = 0;
      var comment, submit, comment_date, submit_date;

      while (latest.length < num && comment_index <= comments.length && submit_index <= submissions.length) {
        if (comments.length > comment_index) {
          comment = comments[comment_index];
          comment_date = moment(comment.created_utc*1000);
        } else {
          comment = null;
        }

        if (submissions.length > submit_index) {
          submit = submissions[submit_index];
          submit_date = moment(submit.created_utc*1000);
        } else {
          submit = null;
        }

        /*
          When a comment or submission is pushed, increase the index, otherwise keep
          the index where it is to compare with the next item.
        */
        if (comment && submit) {
          if (comment_date > submit_date) {
            latest.push(comment);
            comment_index += 1;
          } else {
            latest.push(submit);
            submit_index += 1;
          }
        } else if (comment && !submit) {
          latest.push(comment);
          comment_index += 1;
        } else if (!comment && submit) {
          latest.push(submit);
          submit_index += 1;
        } else {
          // Not enough comments and submissions
          break;
        }
      }

      return latest;
    };

    /*
     Compute the default sorted subreddits alphabetically
     Also, get the length
    */
    function setDefaultSortedArray() {
      defaultSortedArray = $filter('sortSubs')(Object.keys(subs), 'subName', subs);
      subLength = defaultSortedArray.length;
    };

  }]);<commit_msg>Fix: Allow user search to eliminate spaces in input<commit_after>'use strict';

/**
 * @ngdoc factory
 * @name SubSnoopApp.factory
 * @description
 * # subFactory
 * Factory in the SubSnoopApp.
 */
 angular.module('SubSnoopApp')
 .factory('subFactory', ['$http', 'userFactory', '$q', 'moment', '$sce', '$filter', function ($http, userFactory, $q, moment, $sce, $filter) {
    var baseUrl = 'https://www.reddit.com/user/';
    var rawJson = 'raw_json=1';
    var pages = 10;
    var username;
    var promise;
    var after = "0";
    var subLength = 0;
    var defaultSortedArray = [];

    var comments = [];
    var submissions = [];
    var subs = {};
    var commentData = [];
    var submitData = [];
    var subData = {};

    /*
     Callback function to fetch data from user's comments
    */
    window.commentsCallback = function(response) {
      commentData.push(response.data.children);
      after = response.data.after;
      pushData(response.data, 'comments');
    };

    /*
     Callback function to fetch data from user's submissions
    */
    window.submitsCallback = function(response) {
      submitData.push(response.data.children);
      after = response.data.after;
      pushData(response.data, 'submits');
    };

    /*
     User interface for sub factory
    */
    var factory = {
      getData: function(user) {
        resetData();
        username = user;
        var userPromise = userFactory.getData(user);
        return getSubPromise(userPromise);
      },
      getSubData: function() {
        return subData;
      },
      checkUser: function(user) {
        if (!username) {
          return false;
        } else {
          return matchUser(user, username);
        }
      },
      getDefaultSortedArray: function() {
        return defaultSortedArray;
      },
      getSubLength: function() {
        return subLength;
      }
    };
    return factory;

    /*
     Check to see if two username strings match
    */
    function matchUser(user1, user2) {
      return user2.toLowerCase().indexOf(user1.toLowerCase()) >= 0;
    }

    /*
     Configure sub data object, which will be passed to the controllers.
    */
    function setSubData(response) {
      organizeComments(comments);
      organizeSubmitted(submissions);
      setTotalUps();
      setDefaultSortedArray();

      subData = {
        'user': response,
        'comments' : comments.length,
        'submissions' : submissions.length,
        'subs' : subs,
        'firstDate' : getFirstDate(),
        'latest' : getLatest(2)
      }
    };

    /*
     Reset all data to empty lists (used for getting a new user)
    */
    function resetData() {
      after = "0";
      comments = [];
      submissions = [];
      subs = {};
      commentData = [];
      submitData = [];
      subData = {};
    };

    /*
     Only if user promise resolves, then do promise chaining for comments and
     submissions asynchronously
    */
    function getSubPromise(userPromise) {
      var subPromise = userPromise.then(function(response) {
        if (response && matchUser(response.name, username)) {
          var commentPromise = promiseChain('comments', 'commentsCallback');
          var submitPromise = promiseChain('submitted', 'submitsCallback');

          // Resolve both comment and submission promises together
          var dataPromise = $q.all([commentPromise, submitPromise]).then(function() {
            setSubData(response);
            return subData;
          });
          return dataPromise;
        } else {
          return null;
        }
      });
      return subPromise;
    };

    /*
     Make the http request to the Reddit API using JSONP.
    */
    function getJSONP(where, callback) {
      var url = 'https://www.reddit.com/user/'+username+'/'+where+'.json?limit=100&after='+after+'&jsonp='+callback;
      var trustedUrl =  $sce.trustAsResourceUrl(url);
      return $http.jsonp(trustedUrl);
    };

    /*
     Push the comment/submission data to their respective lists
    */
    function pushData(response, where) {
      if (response) {
        var data = response.children;
        for (var i = 0; i < data.length; i++) {
          var item = data[i].data;
          if (where === 'comments') {
            item.type = 'comment';
            comments.push(item);
          } else {
            item.type = 'submit';
            submissions.push(item);
          }
        }
      }
    };

    function getDataList(where) {
      return where === 'comments' ? commentData : submitData;
    };

    /*
     Resolve promise and return data if there is no more requests.
     If there is still an after value, chain the next promise.
    */
    function getPromise(where, callback, promise, index) {
      var promise = promise.then(function() {
        return getDataList(where);
      }, function() {
        if (index === pages-1) {
          return getDataList(where);
        } else {
          return after ? getJSONP(where, callback) : getDataList(where);
        }
      });
      return promise;
    };

    /*
     Chain data promises for fetching comments or submissions.
     Reddit API caps at 1000 comments and submisions each. Only 100 items can be
     fetched at a time, making for at most 10 API requests.
    */
    function promiseChain(where, callback) {
      var promise = getJSONP(where, callback);
      for (var i = 0; i < pages; i++) {
        promise = getPromise(where, callback, promise, i);
      }
      return promise;
    };

    /*
     Grabs the comments and store them in their respective sub object
     as well as other statistics
    */
    function organizeComments(comments) {
      subs = {};
      // Push comments
      for (var i = 0; i < comments.length; i++) {
        var comment = comments[i];
        var subreddit = comment.subreddit;
        var ups = parseInt(comment.ups);

        if (subreddit in subs) {
          var comment_list = subs[subreddit].comments;
          var date = moment(comment.created_utc*1000);

          subs[subreddit].comment_ups += ups;

          comment_list.push(comment);
          if (date > subs[subreddit].recent_comment) {
            subs[subreddit].recent_comment = date;
          }
        } else {
          subs[subreddit] = {};
          subs[subreddit].comments = new Array(comment);
          subs[subreddit].recent_comment = moment(comment.created_utc*1000);
          subs[subreddit].comment_ups = ups;
          subs[subreddit].submissions = [];
          subs[subreddit].submission_ups = 0;
          subs[subreddit].gilded_comments = 0;
          subs[subreddit].gilded_submissions = 0;
          subs[subreddit].count = 0;
        }

        if (comment.gilded > 0) {
          subs[subreddit].gilded_comments += 1;
        }

        subs[subreddit].count += 1;
        subs[subreddit].recent_activity = subs[subreddit].recent_comment;
      }
    };

    /*
     Grabs submissions and stores them in their respective sub object
    */
    function organizeSubmitted(submissions) {
      for (var i = 0; i < submissions.length; i++) {
        var submission = submissions[i];
        var subreddit = submission.subreddit;
        var ups = parseInt(submission.ups);

        if (subreddit in subs && subs[subreddit].submissions.length > 0) {
          var submission_list = subs[subreddit].submissions;
          var date = moment(submission.created_utc*1000);
          var recent_submission = subs[subreddit].recent_submission;

          subs[subreddit].submission_ups += ups;

          submission_list.push(submission);
          if (date > recent_submission) {
            subs[subreddit].recent_submission = date;
          }
        } else {
          if (!(subreddit in subs)) {
            subs[subreddit] = {};
            subs[subreddit].submissions = [];
            subs[subreddit].comments = [];
            subs[subreddit].comment_ups = 0;
            subs[subreddit].gilded_submissions = 0;
            subs[subreddit].count = 0;
          }
          subs[subreddit].submissions.push(submission);
          subs[subreddit].recent_submission = moment(submission.created_utc*1000);
          subs[subreddit].submission_ups = ups;
        }

        if (submission.gilded > 0) {
          subs[subreddit].gilded_submissions += 1;
        }

        if ('recent_comment' in subs[subreddit]) {
          if (subs[subreddit].recent_submission > subs[subreddit].recent_comment) {
            subs[subreddit].recent_activity = subs[subreddit].recent_submission;
          }
        } else {
          subs[subreddit].recent_activity = subs[subreddit].recent_submission;
        }

        subs[subreddit].count += 1;
      }
    };

    /*
     Get the combined total of comment and submission upvotes
    */
    function setTotalUps() {
      for (var sub in subs) {
        subs[sub].total_ups = subs[sub].comment_ups + subs[sub].submission_ups;
      }
    };

    /*
     Get the date of the first post
    */
    function getFirstDate() {
      var dataAvailable;
      var lastComment, lastSubmit, commentDate, submitDate;

      if (comments.length > 0) {
        lastComment = comments[comments.length-1];
        commentDate = moment(lastComment.created_utc*1000);
      }

      if (submissions.length > 0) {
        lastSubmit = submissions[submissions.length-1];
        submitDate = moment(lastSubmit.created_utc*1000);
      }

      if (commentDate && submitDate) {
        dataAvailable = (commentDate < submitDate) ? commentDate : submitDate;
      } else if (commentDate) {
        dataAvailable = commentDate;
      } else if (submitDate) {
        dataAvailable = submitDate;
      }
      return dataAvailable;
    };

    /*
     Get the most recent posts (whether they be comment or submission)
    */
    function getLatest(num) {
      var latest = [];
      var comment_index = 0;
      var submit_index = 0;
      var comment, submit, comment_date, submit_date;

      while (latest.length < num && comment_index <= comments.length && submit_index <= submissions.length) {
        if (comments.length > comment_index) {
          comment = comments[comment_index];
          comment_date = moment(comment.created_utc*1000);
        } else {
          comment = null;
        }

        if (submissions.length > submit_index) {
          submit = submissions[submit_index];
          submit_date = moment(submit.created_utc*1000);
        } else {
          submit = null;
        }

        /*
          When a comment or submission is pushed, increase the index, otherwise keep
          the index where it is to compare with the next item.
        */
        if (comment && submit) {
          if (comment_date > submit_date) {
            latest.push(comment);
            comment_index += 1;
          } else {
            latest.push(submit);
            submit_index += 1;
          }
        } else if (comment && !submit) {
          latest.push(comment);
          comment_index += 1;
        } else if (!comment && submit) {
          latest.push(submit);
          submit_index += 1;
        } else {
          // Not enough comments and submissions
          break;
        }
      }

      return latest;
    };

    /*
     Compute the default sorted subreddits alphabetically
     Also, get the length
    */
    function setDefaultSortedArray() {
      defaultSortedArray = $filter('sortSubs')(Object.keys(subs), 'subName', subs);
      subLength = defaultSortedArray.length;
    };

  }]);<|endoftext|>


<commit_before>/**
 * routes/api.js - Defines /api/search and /api/browse routes for API requests
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 09 Mar 15
 */

var express = require('express');
var request = require('request');
var xml2js = require('xml2js');
var router = express.Router();
var parser = new xml2js.Parser({explicitArray: false});
var parseString = parser.parseString;

var clientKey = require('../lib/clientkey').Key;
var Cache = require('../lib/badcache');
var Podcasts = require('../lib/podcasts');
var isFavorited = require('../lib/users').isFavorited;

/**
 * Routes for api/view URLs
 */
router.get('/view/splash', function (req, res, next) {
    var casts;
    if (req.session.user) {
        res.render('splash', {
            user: req.session.user,
            genres: Podcasts.Genres});
    }
    else {
        res.render('splash-guest', {
            genres: Podcasts.Genres});
    }
});
router.get('/view/podcast/:id', function (req, res, next) {
    var id = req.params.id;
    // HTTP request information on podcast ID via Apple API
    Podcasts.getPodcast(id, function (error, msg) {
        res.render('error', { message: msg, error: error});
    }, function (podcast) {
        request(podcast.feedUrl, function (error, response, body) {
            // Data returned in XML format, and must be parsed.
            if (error) {
                console.log('Error retrieving feed:');
                console.log('\t' + JSON.stringify(error));
                res.render('error', {error: error});
            }
            else {
                parseString(body, function (err, obj) {
                    // Render client 'podcast' page/view, passing necessary data.
                    if (err) {
                        console.log('Error parsing podcast feed:');
                        console.log('\t' + JSON.stringify(err));
                        res.render('error', {error: err});
                    }
                    else {
                        // Increase podcast popularity
                        Podcasts.modifyPodcastPoints(podcast._id, 1);
                        res.render('podcast', {id: id,
                                               user: req.session.user,
                                               isFavorited: isFavorited,
                                               title: podcast.title,
                                               podcast: podcast,
                                               safetitle: id + '/' + podcast.title_uri,
                                               feed: obj.rss.channel });
                    }
                });
            }
        });
    });
});
router.get('/castcat/:gid', function (req, res, next) {
    if (req.session.user) {
        Cache.aggregate_cat(req.params.gid,
                            req.session.user.subscriptions,
                            function (err, msg) {
            console.log(msg);
            console.log(err);
            res.json({error: err, message: msg});
        }, function (data) {
            res.json({podcasts: data[0], favorites: data[1]});
        });
    } else {
        Cache.aggregate_cat(req.params.gid, false, function (err, msg) {
            console.log(msg);
            console.log(err);
            res.json({error: err, message: msg});
        }, function (data) {
            res.json({podcasts: data[0]});
        });
    }
});

/**
 * Route for api/clientkey URL
 */
router.post('/clientkey', function (req, res, next) {
    if (!req.body.key) {
        res.json({status: 400,
                  message: "Bad Request"});
    }
    else if (req.body.key !== 'fish') {
        res.json({status: 401,
                  message: "Unauthorized"});
    }
    else {
        res.json({status: 200,
                  message: clientKey});
    }
});

/**
 * Route for api/search URL
 */
router.get('/search', function (req, res, next) {
    var api = 'https://itunes.apple.com/search?entity=podcast&term=';
    // Name is ambiguous, and might confuse things when we implement local searching by station/artist/etc.
    // For now, it's best to use `term` to avoid confusion.
    var queryURL = api + req.query.term;

    if (!req.query.term) return res.json([]);

    console.log('url: ' + queryURL);

    // Send requested search data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Parse JSON results
        var results = JSON.parse(body).results;
        if (results) {
            // Cache results
            Podcasts.cacheSearchResults(results, function (err, msg) {
                console.log(msg);
                console.log(err);
                res.json({error: err, message: msg});
            }, function (data) {
                Podcasts.searchByTerm(req.query.term, 0, function (pcasts) {
                    if (!req.session.searchcache) req.session.searchcache = {};
                    req.session.searchcache[req.query.term] = pcasts;
                    res.json(pcasts);
                });
            });
        } else {
            res.json([]);
        }
    });
});

/**
 * Route for api/browse URL
 */
router.get('/browse', function (req, res, next) {
    var a, favs = false, user = req.session.user, genre, gid = req.query.cat;
    if (!gid || !Podcasts.Genres[gid]) {
        res.json({});
    }
    else {
        genre = Podcasts.Genres[gid];
        if (user && user.subscriptions &&
                user.subscriptions[gid] &&
                user.subscriptions[gid].length > 0) {
            favs = user.subscriptions[gid];
        }
        Podcasts.getByCategory(genre, false, favs, function (pcasts) {
            if (favs) {
                Podcasts.getPodcasts(favs, function (err, msg) {
                    console.log('Unexpected error occured: ' + err.status);
                    console.log('\t' + msg);
                    a = {podcasts: pcasts,
                             error: err, message: msg};
                    req.session.browsecache[gid] = a;
                    res.json(a);
                }, function (f_pcasts) {
                    a = {podcasts: pcasts, favorites: f_pcasts};
                    req.session.browsecache[gid] = a;
                    res.json(a);
                });
            }
            else {
                a = {podcasts: pcasts};
                req.session.browsecache[gid] = a;
                res.json(a);
            }
        });
    }
});

/**
 * Route for api/cachesearch URL
 */
router.get('/cachesearch', function (req, res, next) {
    if (!req.query.term) {
        res.json([]);
    }
    else if (!req.session.searchcache || !req.session.searchcache[req.query.term]) {
        res.json(false);
    }
    else {
        res.json(req.session.searchcache[req.query.term]);
    }
});

/**
 * Route for api/cachebrowse URL
 */
router.get('/cachebrowse', function (req, res, next) {
    if (!req.query.cat) {
        res.json({});
    }
    else if (!req.session.browsecache || !req.session.browsecache[req.query.cat]) {
        res.json(false);
    }
    else {
        res.json(req.session.brwosecache[req.query.cat]);
    }
});

/**
 * Route for api/quicksearch URL
 */
router.get('/quicksearch', function (req, res, next) {
    if (!req.query.term) {
        res.json([]);
    }
    else {
        Podcasts.searchByTerm(req.query.term, 25, function (data) {
            res.json(data);
        });
    }
});

// Expose routes
module.exports = router;
<commit_msg>[Fix] No initialization of session.browsecache<commit_after>/**
 * routes/api.js - Defines /api/search and /api/browse routes for API requests
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 09 Mar 15
 */

var express = require('express');
var request = require('request');
var xml2js = require('xml2js');
var router = express.Router();
var parser = new xml2js.Parser({explicitArray: false});
var parseString = parser.parseString;

var clientKey = require('../lib/clientkey').Key;
var Cache = require('../lib/badcache');
var Podcasts = require('../lib/podcasts');
var isFavorited = require('../lib/users').isFavorited;

/**
 * Routes for api/view URLs
 */
router.get('/view/splash', function (req, res, next) {
    var casts;
    if (req.session.user) {
        res.render('splash', {
            user: req.session.user,
            genres: Podcasts.Genres});
    }
    else {
        res.render('splash-guest', {
            genres: Podcasts.Genres});
    }
});
router.get('/view/podcast/:id', function (req, res, next) {
    var id = req.params.id;
    // HTTP request information on podcast ID via Apple API
    Podcasts.getPodcast(id, function (error, msg) {
        res.render('error', { message: msg, error: error});
    }, function (podcast) {
        request(podcast.feedUrl, function (error, response, body) {
            // Data returned in XML format, and must be parsed.
            if (error) {
                console.log('Error retrieving feed:');
                console.log('\t' + JSON.stringify(error));
                res.render('error', {error: error});
            }
            else {
                parseString(body, function (err, obj) {
                    // Render client 'podcast' page/view, passing necessary data.
                    if (err) {
                        console.log('Error parsing podcast feed:');
                        console.log('\t' + JSON.stringify(err));
                        res.render('error', {error: err});
                    }
                    else {
                        // Increase podcast popularity
                        Podcasts.modifyPodcastPoints(podcast._id, 1);
                        res.render('podcast', {id: id,
                                               user: req.session.user,
                                               isFavorited: isFavorited,
                                               title: podcast.title,
                                               podcast: podcast,
                                               safetitle: id + '/' + podcast.title_uri,
                                               feed: obj.rss.channel });
                    }
                });
            }
        });
    });
});
router.get('/castcat/:gid', function (req, res, next) {
    if (req.session.user) {
        Cache.aggregate_cat(req.params.gid,
                            req.session.user.subscriptions,
                            function (err, msg) {
            console.log(msg);
            console.log(err);
            res.json({error: err, message: msg});
        }, function (data) {
            res.json({podcasts: data[0], favorites: data[1]});
        });
    } else {
        Cache.aggregate_cat(req.params.gid, false, function (err, msg) {
            console.log(msg);
            console.log(err);
            res.json({error: err, message: msg});
        }, function (data) {
            res.json({podcasts: data[0]});
        });
    }
});

/**
 * Route for api/clientkey URL
 */
router.post('/clientkey', function (req, res, next) {
    if (!req.body.key) {
        res.json({status: 400,
                  message: "Bad Request"});
    }
    else if (req.body.key !== 'fish') {
        res.json({status: 401,
                  message: "Unauthorized"});
    }
    else {
        res.json({status: 200,
                  message: clientKey});
    }
});

/**
 * Route for api/search URL
 */
router.get('/search', function (req, res, next) {
    var api = 'https://itunes.apple.com/search?entity=podcast&term=';
    // Name is ambiguous, and might confuse things when we implement local searching by station/artist/etc.
    // For now, it's best to use `term` to avoid confusion.
    var queryURL = api + req.query.term;

    if (!req.query.term) return res.json([]);

    console.log('url: ' + queryURL);

    // Send requested search data received from Apple API back to caller.
    request(queryURL, function (error, response, body) {
        // Parse JSON results
        var results = JSON.parse(body).results;
        if (results) {
            // Cache results
            Podcasts.cacheSearchResults(results, function (err, msg) {
                console.log(msg);
                console.log(err);
                res.json({error: err, message: msg});
            }, function (data) {
                Podcasts.searchByTerm(req.query.term, 0, function (pcasts) {
                    if (!req.session.searchcache) req.session.searchcache = {};
                    req.session.searchcache[req.query.term] = pcasts;
                    res.json(pcasts);
                });
            });
        } else {
            res.json([]);
        }
    });
});

/**
 * Route for api/browse URL
 */
router.get('/browse', function (req, res, next) {
    var a, favs = false, user = req.session.user, genre, gid = req.query.cat;
    if (!gid || !Podcasts.Genres[gid]) {
        res.json({});
    }
    else {
        genre = Podcasts.Genres[gid];
        if (user && user.subscriptions &&
                user.subscriptions[gid] &&
                user.subscriptions[gid].length > 0) {
            favs = user.subscriptions[gid];
        }
        Podcasts.getByCategory(genre, false, favs, function (pcasts) {
            if (!req.session.browsecache) req.session.browsecache = {};
            if (favs) {
                Podcasts.getPodcasts(favs, function (err, msg) {
                    console.log('Unexpected error occured: ' + err.status);
                    console.log('\t' + msg);
                    a = {podcasts: pcasts,
                             error: err, message: msg};
                    req.session.browsecache[gid] = a;
                    res.json(a);
                }, function (f_pcasts) {
                    a = {podcasts: pcasts, favorites: f_pcasts};
                    req.session.browsecache[gid] = a;
                    res.json(a);
                });
            }
            else {
                a = {podcasts: pcasts};
                req.session.browsecache[gid] = a;
                res.json(a);
            }
        });
    }
});

/**
 * Route for api/cachesearch URL
 */
router.get('/cachesearch', function (req, res, next) {
    if (!req.query.term) {
        res.json([]);
    }
    else if (!req.session.searchcache || !req.session.searchcache[req.query.term]) {
        res.json(false);
    }
    else {
        res.json(req.session.searchcache[req.query.term]);
    }
});

/**
 * Route for api/cachebrowse URL
 */
router.get('/cachebrowse', function (req, res, next) {
    if (!req.query.cat) {
        res.json({});
    }
    else if (!req.session.browsecache || !req.session.browsecache[req.query.cat]) {
        res.json(false);
    }
    else {
        res.json(req.session.brwosecache[req.query.cat]);
    }
});

/**
 * Route for api/quicksearch URL
 */
router.get('/quicksearch', function (req, res, next) {
    if (!req.query.term) {
        res.json([]);
    }
    else {
        Podcasts.searchByTerm(req.query.term, 25, function (data) {
            res.json(data);
        });
    }
});

// Expose routes
module.exports = router;
<|endoftext|>


<commit_before>(function (angular) {
	'use strict';

	angular.module('scroll-spy', [])
		.directive('scrollSpy', scrollSpyDirective)
		.directive('scrollSpyArea', scrollSpyAreaDirective)
		.directive('spyItem', spyItemDirective)
		.directive('spyListItem', spyListItemDirective)
		.directive('spyGroup', spyGroupDirective);

	scrollSpyController.$inject = ['$timeout', '$document', '$window'];
	scrollSpyAreaDirective.$inject = ['$window'];

	function scrollSpyController ($timeout, $document, $window) {
		var self = this;

		// div elements associated by target id
		this.groups = {};
		// anchor elements associated by target id
		this.anchors = {};
		// list item element contained more than one anchor
		this.listItems = {};

		var scrollableElement = null,
			defaultOffset = 10,
			offsets = [],
			targets = [],
			activeTarget = null,
			initScrollHeight = 0,
			activeTargetUpdated = false;

		var mapValues = function (obj) {
			return Object.keys(obj)
				.map(function (key) {
					return obj[key];
				});
		};

		var getScrollHeight = function () {
			return scrollableElement.prop('scrollHeight') || Math.max($document[0].body.scrollHeight, $document[0].scrollHeight);
		};

		var refresh = function () {

			offsets = [];
			targets = [];
			activeTarget = null;
			initScrollHeight = getScrollHeight();

			Object.keys(self.groups)
				.map(function (groupTarget) {
					var groupElement = self.groups[groupTarget];
					if (groupElement.css('visibility') !== 'hidden') {
						return {
							offset: groupElement.prop('offsetTop'),
							target: groupTarget
						}
					}
				})
				.sort(function (a, b) {
					return a.offset - b.offset
				})
				.forEach(function (mappedObj) {
					offsets.push(mappedObj.offset);
					targets.push(mappedObj.target);
				});
		};

		var activateItem = function (target) {
			activeTarget = target;
			clear();

			if (self.listItems[target]) {
				self.listItems[target].parent().addClass('active');
			}
			if (self.anchors[target]) {
				self.anchors[target].parent().addClass('active');
			}

			self.groups[target].addClass('active');
		};

		var clear = function () {
			mapValues(self.listItems)
				.forEach(function (li) {
					li.removeClass('active');
				});

			mapValues(self.anchors)
				.forEach(function(anchor) {
					anchor.parent().removeClass('active');
				});

			mapValues(self.groups)
				.forEach(function(group) {
					group.removeClass('active');
				});
		};

		var scrollTopWithinGroup = function (scrollTop, groupIndex) {
			return activeTarget !== targets[groupIndex]
				&& scrollTop >= offsets[groupIndex] - defaultOffset
				&& (offsets[groupIndex + 1] === undefined || scrollTop < offsets[groupIndex + 1] - defaultOffset);
		};

		this.update = function () {
			if (activeTargetUpdated) {
				return;
			}
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var scrollHeight = getScrollHeight();
			var maxScroll = defaultOffset + scrollHeight - scrollableElement.prop('offsetHeight');

			if (scrollHeight !== initScrollHeight) {
				refresh();
			}

			if (scrollTop >= maxScroll) {
				var item = scrollTop === defaultOffset ? targets[0] : targets[targets.length - 1];
				activateItem(item);
				return;
			}

			if (activeTarget && scrollTop < offsets[0]) {
				activeTarget = null;
				clear();
				return;
			}

			for (var i = 0; i < offsets.length; i++) {
				if (scrollTopWithinGroup(scrollTop, i)) {
					activateItem(targets[i]);
					return;
				}
			}
		};

		this.activate = function (element, offset) {
			scrollableElement = element;
			defaultOffset = offset;

			element.bind('scroll', self.update);
			var window = angular.element($window);
			window.bind('resize', function () {
				$timeout(self.update, 100);
			});

			refresh();
			self.update();
		};

		this.deactivate = function (element) {
			element.unbind('scroll');
		};

		this.activateItemOnClick = function (target) {
			activeTargetUpdated = true;
			activateItem(target);
			scrollableElement[0].scrollTop = self.groups[target].prop('offsetTop');
			$timeout(function () {
				activeTargetUpdated = false;
			}, 100);
		};
	}

	function scrollSpyDirective () {
		return {
			restrict: 'A',
			controller: scrollSpyController,
			link: function (scope, elem, attrs, ctrl) {	}
		};
	}

	function scrollSpyAreaDirective ($window) {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {

				var offset = parseInt(attrs.spyOffset);
				scope.$watch(function () {
					return $window.getComputedStyle(elem[0]).overflowY;
				}, function (value) {
					if (value !== 'hidden') {
						ctrl.activate(elem, offset);
					} else {
						ctrl.deactivate(elem);
					}
				});

				scope.$watch(function () {
					return elem[0].offsetHeight;
				}, function (value) {
					var overflowY = $window.getComputedStyle(elem[0]).overflowY;
					if (overflowY !== 'hidden') {
						ctrl.update();
					}
				});
			}
		}
	};

	function spyItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				});
			}
		}
	}

	function spyListItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.listItems[attrs.target] = ctrl.anchors[attrs.spyListItem];
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				})
			}
		}
	}

	function spyGroupDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.groups[attrs.id] = elem;
			}
		}
	}
})(angular);
<commit_msg>Refresh-update added after spy group adding<commit_after>(function (angular) {
	'use strict';

	angular.module('scroll-spy', [])
		.directive('scrollSpy', scrollSpyDirective)
		.directive('scrollSpyArea', scrollSpyAreaDirective)
		.directive('spyItem', spyItemDirective)
		.directive('spyListItem', spyListItemDirective)
		.directive('spyGroup', spyGroupDirective);

	scrollSpyController.$inject = ['$timeout', '$document', '$window'];
	scrollSpyAreaDirective.$inject = ['$window'];

	function scrollSpyController ($timeout, $document, $window) {
		var self = this;

		// div elements associated by target id
		this.groups = {};
		// anchor elements associated by target id
		this.anchors = {};
		// list item element contained more than one anchor
		this.listItems = {};

		var scrollableElement = null,
			defaultOffset = 10,
			offsets = [],
			targets = [],
			activeTarget = null,
			initScrollHeight = 0,
			activeTargetUpdated = false;

		var mapValues = function (obj) {
			return Object.keys(obj)
				.map(function (key) {
					return obj[key];
				});
		};

		var getScrollHeight = function () {
			return scrollableElement.prop('scrollHeight') || Math.max($document[0].body.scrollHeight, $document[0].scrollHeight);
		};

		var refresh = function () {

			offsets = [];
			targets = [];
			activeTarget = null;
			initScrollHeight = getScrollHeight();

			console.log('--- In Refresh ---');
			console.log(self.groups);
			console.log('--- ^ self.groups ---');

			Object.keys(self.groups)
				.map(function (groupTarget) {
					var groupElement = self.groups[groupTarget];
					if (groupElement.css('visibility') !== 'hidden') {
						return {
							offset: groupElement.prop('offsetTop'),
							target: groupTarget
						}
					}
				})
				.sort(function (a, b) {
					return a.offset - b.offset
				})
				.forEach(function (mappedObj) {
					offsets.push(mappedObj.offset);
					targets.push(mappedObj.target);
				});
		};

		var getParentListItem = function (current) {
			var parent = current.parent();
			while (parent[0] && parent[0].tagName !== 'LI') {
				parent = parent.parent();
			}
			return parent;
		};

		var activateItem = function (target) {
			console.log('--- In activateItem ---');
			console.log(target);
			console.log('--- ^ target ---');
			if (!target) {
				return;
			}
			activeTarget = target;
			clear();

			if (self.listItems[target]) {
				getParentListItem(self.listItems[target]).addClass('active');
			}
			if (self.anchors[target]) {
				getParentListItem(self.anchors[target]).addClass('active');
			}

			self.groups[target].addClass('active');
		};

		var clear = function () {
			mapValues(self.listItems)
				.forEach(function (li) {
					getParentListItem(li).removeClass('active');
				});

			mapValues(self.anchors)
				.forEach(function(anchor) {
					getParentListItem(anchor).removeClass('active');
				});

			mapValues(self.groups)
				.forEach(function(group) {
					group.removeClass('active');
				});
		};

		var scrollTopWithinGroup = function (scrollTop, groupIndex) {
			return activeTarget !== targets[groupIndex]
				&& scrollTop >= offsets[groupIndex] - defaultOffset
				&& (offsets[groupIndex + 1] === undefined || scrollTop < offsets[groupIndex + 1] - defaultOffset);
		};

		this.refresh = refresh;

		this.update = function () {
			console.log('--- In Update ---');
			if (activeTargetUpdated) {
				return;
			}
			var scrollTop = scrollableElement.prop('scrollTop') + defaultOffset;
			var scrollHeight = getScrollHeight();
			var maxScroll = defaultOffset + scrollHeight - scrollableElement.prop('offsetHeight');

			if (scrollHeight !== initScrollHeight) {
				refresh();
			}

			if (scrollTop >= maxScroll) {
				var item = scrollTop === defaultOffset ? targets[0] : targets[targets.length - 1];
				if (!activeTarget) {
					activateItem(item);
				}
				return;
			}

			if (activeTarget && scrollTop < offsets[0]) {
				activeTarget = null;
				clear();
				return;
			}

			for (var i = 0; i < offsets.length; i++) {
				if (scrollTopWithinGroup(scrollTop, i)) {
					activateItem(targets[i]);
					return;
				}
			}
		};

		this.activate = function (element, offset) {
			scrollableElement = element;
			defaultOffset = offset;

			element.bind('scroll', self.update);
			var window = angular.element($window);
			window.bind('resize', function () {
				$timeout(self.update, 100);
			});

			refresh();
			self.update();
		};

		this.deactivate = function (element) {
			element.unbind('scroll');
		};

		this.activateItemOnClick = function (target) {
			activeTargetUpdated = true;
			activateItem(target);
			scrollableElement[0].scrollTop = self.groups[target].prop('offsetTop');
			$timeout(function () {
				activeTargetUpdated = false;
			}, 100);
		};
	}

	function scrollSpyDirective () {
		return {
			restrict: 'A',
			controller: scrollSpyController,
			link: function (scope, elem, attrs, ctrl) {	}
		};
	}

	function scrollSpyAreaDirective ($window) {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {

				var offset = parseInt(attrs.spyOffset);
				scope.$watch(function () {
					return $window.getComputedStyle(elem[0]).overflowY;
				}, function (value) {
					if (value !== 'hidden') {
						ctrl.activate(elem, offset);
					} else {
						ctrl.deactivate(elem);
					}
				});

				scope.$watch(function () {
					return elem[0].offsetHeight;
				}, function (value) {
					var overflowY = $window.getComputedStyle(elem[0]).overflowY;
					if (overflowY !== 'hidden') {
						ctrl.update();
					}
				});
			}
		}
	};

	function spyItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				console.log('--- In spy item ---');
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				});
			}
		}
	}

	function spyListItemDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				ctrl.listItems[attrs.target] = ctrl.anchors[attrs.spyListItem];
				ctrl.anchors[attrs.target] = elem;
				elem.bind('click', function () {
					ctrl.activateItemOnClick(attrs.target);
				})
			}
		}
	}

	function spyGroupDirective () {
		return {
			restrict: 'A',
			require: '^scrollSpy',
			link: function (scope, elem, attrs, ctrl) {
				console.log('--- In spy group ---');
				ctrl.groups[attrs.id] = elem;

				ctrl.refresh();
				ctrl.update();
			}
		}
	}
})(angular);
<|endoftext|>