<commit_before>var fs = require('fs');  //puesto por ahora

var request = require('request');
var express = require('express'),
      app = express(),
      server = require('http').createServer(app),

      elemParadas = [],
      paradas = [];

var paradasant = [];
var _recorridos = [];

done = false;

var io = require('socket.io')(server);  //funciona

app.use( express.static(__dirname + '/public'));

  app.use (function (req, res, next) {
          res.header("Access-Control-Allow-Origin", "*");
          res.header("Access-Control-Allow-Headers", "X-Requested-Width");

  });

var arrayRecorridos = [
						   ["Cordón a Sayago","public/Recorridos/CordonSayago.js"],
						   ["Sayago a Cordón","public/Recorridos/sayagoCordon.js"],
						   ["Reducto a FIng","public/Recorridos/reductoFing.js"],
						   ["Toledo a FIng","public/Recorridos/toledoFing.js"],
						   ["Ciudad Vieja a Prado","public/Recorridos/cViejaPrado.js"],
						   ["Prado a Ciudad Vieja","public/Recorridos/pradoCVieja.js"],
						   ["Pque Rodó a Prado","public/Recorridos/pqeRodoPrado.js"],
						   ["Ciudad Vieja a Pque Rodó","public/Recorridos/cvpr.js"],
						   ["Pque Rodó a Ciudad Vieja","public/Recorridos/prcv.js"],
						   ["Pocitos a Ciudad Vieja","public/Recorridos/pocitosCVieja.js"],
						   ["CVieja a Pocitos","public/Recorridos/cViejaPocitos.js"],
						   ["Ciudad Vieja a Obelisco","public/Recorridos/cvObelisco.js"],
						   ["Comercial a Pcio. Legislativo","public/Recorridos/comercialPcioLeg.js"],
						   ["Comercial a CV","public/Recorridos/ComercialCV.js"]
					   ];

var intReq, intSend;

//provisorio
  var biciamigos = null;
  var infladores = null;
  var bicipuntos = null;
  var bicicleterias = null;
  var repechos = null;
  var seccionales = null;
  var talleres = null;
  var paseoRambla = null;
  var paseoPeniarol = null;
  var paseoPrado = null;

  fs.readFile('public/Mapas/biciamigos.js', 'utf8', function (err,data1) {

        if (err) {
          return console.log(err);
        }

        biciamigos = data1;
        //console.log(data1);
    });
  fs.readFile('public/Mapas/infladores3.js', 'utf8', function (err,data2) {

        if (err) {
          return console.log(err);
        }

        infladores = data2;

    });

  fs.readFile('public/Mapas/bicipuntos.js', 'utf8', function (err,data3) {

        if (err) {
          return console.log(err);
        }

        bicipuntos = data3;

    });
  fs.readFile('public/Mapas/bicicleterias.js', 'utf8', function (err,data4) {

        if (err) {
          return console.log(err);
        }

        bicicleterias = data4;
        //console.log(data2);
    });
  fs.readFile('public/Mapas/repechos.js', 'utf8', function (err,data5) {

        if (err) {
          return console.log(err);
        }
        repechos = data5;

    });
  fs.readFile('public/Mapas/seccionales.js', 'utf8', function (err,data6) {

        if (err) {
          return console.log(err);
        }

        seccionales = data6;
        //console.log(data2);
    });

  fs.readFile('public/Mapas/talleres.js', 'utf8', function (err,data7) {

        if (err) {
          return console.log(err);
        }

        talleres = data7;
        //console.log(data2);
    });

  fs.readFile('public/Mapas/CVyrambla.js', 'utf8', function (err,data8) {

        if (err) {
          return console.log(err);
        }
        paseoRambla = data8;
    });

  fs.readFile('public/Mapas/paseoPeniarol.js', 'utf8', function (err,data9) {

        if (err) {
          return console.log(err);
        }

        paseoPeniarol = data9;

    });

  fs.readFile('public/Mapas/paseoPrado.js', 'utf8', function (err,data10) {

        if (err) {
          return console.log(err);
        }

        paseoPrado = data10;

    });

  io.sockets.on('connection', function (socket) {
    socket.emit('biciamigos', biciamigos);
    socket.emit('bicipuntos', bicipuntos);
    socket.emit('bicicleterias', bicicleterias);
    socket.emit('infladores', infladores);
    socket.emit('talleres', talleres);
    socket.emit('repechos', repechos);
    socket.emit('seccionales',seccionales);
    socket.emit('paseoRambla',paseoRambla);
    socket.emit('paseoPeniarol',paseoPeniarol);
    socket.emit('paseoPrado',paseoPrado);

    io.sockets.on('error', function() {
      io.connect(host, {
          'force new connection': true
      });
    });

  });

 //Recorridos

 // enviar recorrido

 fs.readFile(arrayRecorridos[13][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[13] = data;
     });

 fs.readFile(arrayRecorridos[12][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[12] = data;
     });

fs.readFile(arrayRecorridos[11][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[11] = data;

     });

fs.readFile(arrayRecorridos[10][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[10] = data;

     });
fs.readFile(arrayRecorridos[9][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[9] = data;

     });

 fs.readFile(arrayRecorridos[8][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[8] = data;

     });

fs.readFile(arrayRecorridos[7][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[7] = data;

     });

fs.readFile(arrayRecorridos[6][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[6] = data;

     });

fs.readFile(arrayRecorridos[5][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[5] = data;

 });

fs.readFile(arrayRecorridos[4][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[4] = data;

 });

 fs.readFile(arrayRecorridos[3][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[3] = data;

 });

 fs.readFile(arrayRecorridos[2][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[2] = data;

 });

fs.readFile(arrayRecorridos[1][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[1] = data;

     });

fs.readFile(arrayRecorridos[0][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[0] = data;

     });

io.sockets.on("connection", function(socket) {

   socket.on("recorridos0", function(data0){

      socket.emit("recorridos0", _recorridos[0]);

     });

     socket.on("recorridos1", function(data1){

        socket.emit("recorridos1",_recorridos[1]);

    });

    socket.on("recorridos2", function(data2){
		socket.emit("recorridos2",_recorridos[2]);
    });

    socket.on("recorridos3", function(data3){
		socket.emit("recorridos3",_recorridos[3]);
    });

    socket.on("recorridos4", function(data4){
		socket.emit("recorridos4",_recorridos[4]);
    });

    socket.on("recorridos5", function(data5){
		socket.emit("recorridos5",_recorridos[5]);
    });

    socket.on("recorridos6", function(data6){
		socket.emit("recorridos6",_recorridos[6]);
    });

	socket.on("recorridos7", function(data7){
		socket.emit("recorridos7",_recorridos[7]);
    });

    socket.on("recorridos8", function(data8){
		socket.emit("recorridos8",_recorridos[8]);
    });

    socket.on("recorridos9", function(data9){
		socket.emit("recorridos9",_recorridos[9]);
    });

    socket.on("recorridos10", function(data10){
		socket.emit("recorridos10",_recorridos[10]);
    });

    socket.on("recorridos11", function(data11){
		socket.emit("recorridos11",_recorridos[11]);
    });

    socket.on("recorridos12", function(data12){

     socket.emit("recorridos12",_recorridos[12]);
    });

    socket.on("recorridos13", function(data13){

        socket.emit("recorridos13",_recorridos[13]);

    });

  });
//});

var d = new Date();
var h = d.getHours();

// Hora de de funcionamiento de las estaciones Plan Movete 7-21hs
//if ((h < 21) && (h >= 7))
//{
  io.sockets.on("connection", function(socket) {

    socket.on("paradas", function(data){
		request('http://movete.montevideo.gub.uy/index.php?option=com_content&view=article&id=1&Itemid=2', function (error, response, html) {
			if (!error && response.statusCode == 200) {
			  html = html.replace(/(\n|\r)/g,'');

			  /* Con esta exp reg obtengo solo el vector, índice 2 del array */

			  var re = /(var\s*paradas\s*=(\s*\[(.*?)\]));/;  //Funciona!!

			  var res = re.exec(html);  //obtenemos un string
			  // res[0] - todo el string var paradas = [..]
			  // res[2] - solo la parte entre [], los más externos

			  //saco [] más externos
			  var paradas1 = res[2].trim();
			  var paradas1 = paradas1.slice(0,res[2].length-1);  //desde 1 y mo 0

				if (paradas1.length > 0)
					{
						socket.emit('paradas', paradas1);
					}

			}
			else
				console.log("sin datos");

		  });  //request

	}); //socket paradas

  }); //socket connection

//} //if hora

 io.sockets.on('error', function() {
	  io.connect(host, {
		'force new connection': true
	  });
	 });

io.sockets.on('disconnect', function(){

});

 server.listen(process.env.PORT || 5000);
<commit_msg>redireccion https<commit_after>var fs = require('fs');  //puesto por ahora

var request = require('request');
var express = require('express'),
      app = express(),
      server = require('http').createServer(app),

      elemParadas = [],
      paradas = [];

var paradasant = [];
var _recorridos = [];

done = false;

var io = require('socket.io')(server);  //funciona

app.use('/*', function(req, res){
  if(req.headers['x-forwarded-proto']!=='https'){
    res.redirect(301, 'https://www.bicimap.uy'+req.url);
  }
  res.sendFile(__dirname + '/index.html');
});

app.use( express.static(__dirname + '/public'));

  app.use (function (req, res, next) {
          res.header("Access-Control-Allow-Origin", "*");
          res.header("Access-Control-Allow-Headers", "X-Requested-Width");

  });

var arrayRecorridos = [
						   ["Cordón a Sayago","public/Recorridos/CordonSayago.js"],
						   ["Sayago a Cordón","public/Recorridos/sayagoCordon.js"],
						   ["Reducto a FIng","public/Recorridos/reductoFing.js"],
						   ["Toledo a FIng","public/Recorridos/toledoFing.js"],
						   ["Ciudad Vieja a Prado","public/Recorridos/cViejaPrado.js"],
						   ["Prado a Ciudad Vieja","public/Recorridos/pradoCVieja.js"],
						   ["Pque Rodó a Prado","public/Recorridos/pqeRodoPrado.js"],
						   ["Ciudad Vieja a Pque Rodó","public/Recorridos/cvpr.js"],
						   ["Pque Rodó a Ciudad Vieja","public/Recorridos/prcv.js"],
						   ["Pocitos a Ciudad Vieja","public/Recorridos/pocitosCVieja.js"],
						   ["CVieja a Pocitos","public/Recorridos/cViejaPocitos.js"],
						   ["Ciudad Vieja a Obelisco","public/Recorridos/cvObelisco.js"],
						   ["Comercial a Pcio. Legislativo","public/Recorridos/comercialPcioLeg.js"],
						   ["Comercial a CV","public/Recorridos/ComercialCV.js"]
					   ];

var intReq, intSend;

//provisorio
  var biciamigos = null;
  var infladores = null;
  var bicipuntos = null;
  var bicicleterias = null;
  var repechos = null;
  var seccionales = null;
  var talleres = null;
  var paseoRambla = null;
  var paseoPeniarol = null;
  var paseoPrado = null;

  fs.readFile('public/Mapas/biciamigos.js', 'utf8', function (err,data1) {

        if (err) {
          return console.log(err);
        }

        biciamigos = data1;
        //console.log(data1);
    });
  fs.readFile('public/Mapas/infladores3.js', 'utf8', function (err,data2) {

        if (err) {
          return console.log(err);
        }

        infladores = data2;

    });

  fs.readFile('public/Mapas/bicipuntos.js', 'utf8', function (err,data3) {

        if (err) {
          return console.log(err);
        }

        bicipuntos = data3;

    });
  fs.readFile('public/Mapas/bicicleterias.js', 'utf8', function (err,data4) {

        if (err) {
          return console.log(err);
        }

        bicicleterias = data4;
        //console.log(data2);
    });
  fs.readFile('public/Mapas/repechos.js', 'utf8', function (err,data5) {

        if (err) {
          return console.log(err);
        }
        repechos = data5;

    });
  fs.readFile('public/Mapas/seccionales.js', 'utf8', function (err,data6) {

        if (err) {
          return console.log(err);
        }

        seccionales = data6;
        //console.log(data2);
    });

  fs.readFile('public/Mapas/talleres.js', 'utf8', function (err,data7) {

        if (err) {
          return console.log(err);
        }

        talleres = data7;
        //console.log(data2);
    });

  fs.readFile('public/Mapas/CVyrambla.js', 'utf8', function (err,data8) {

        if (err) {
          return console.log(err);
        }
        paseoRambla = data8;
    });

  fs.readFile('public/Mapas/paseoPeniarol.js', 'utf8', function (err,data9) {

        if (err) {
          return console.log(err);
        }

        paseoPeniarol = data9;

    });

  fs.readFile('public/Mapas/paseoPrado.js', 'utf8', function (err,data10) {

        if (err) {
          return console.log(err);
        }

        paseoPrado = data10;

    });

  io.sockets.on('connection', function (socket) {
    socket.emit('biciamigos', biciamigos);
    socket.emit('bicipuntos', bicipuntos);
    socket.emit('bicicleterias', bicicleterias);
    socket.emit('infladores', infladores);
    socket.emit('talleres', talleres);
    socket.emit('repechos', repechos);
    socket.emit('seccionales',seccionales);
    socket.emit('paseoRambla',paseoRambla);
    socket.emit('paseoPeniarol',paseoPeniarol);
    socket.emit('paseoPrado',paseoPrado);

    io.sockets.on('error', function() {
      io.connect(host, {
          'force new connection': true
      });
    });

  });

 //Recorridos

 // enviar recorrido

 fs.readFile(arrayRecorridos[13][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[13] = data;
     });

 fs.readFile(arrayRecorridos[12][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[12] = data;
     });

fs.readFile(arrayRecorridos[11][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[11] = data;

     });

fs.readFile(arrayRecorridos[10][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[10] = data;

     });
fs.readFile(arrayRecorridos[9][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[9] = data;

     });

 fs.readFile(arrayRecorridos[8][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[8] = data;

     });

fs.readFile(arrayRecorridos[7][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[7] = data;

     });

fs.readFile(arrayRecorridos[6][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[6] = data;

     });

fs.readFile(arrayRecorridos[5][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[5] = data;

 });

fs.readFile(arrayRecorridos[4][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[4] = data;

 });

 fs.readFile(arrayRecorridos[3][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[3] = data;

 });

 fs.readFile(arrayRecorridos[2][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[2] = data;

 });

fs.readFile(arrayRecorridos[1][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[1] = data;

     });

fs.readFile(arrayRecorridos[0][1], 'utf8', function (err,data) {
        if (err) {
          return console.log(err);
        }
        _recorridos[0] = data;

     });

io.sockets.on("connection", function(socket) {

   socket.on("recorridos0", function(data0){

      socket.emit("recorridos0", _recorridos[0]);

     });

     socket.on("recorridos1", function(data1){

        socket.emit("recorridos1",_recorridos[1]);

    });

    socket.on("recorridos2", function(data2){
		socket.emit("recorridos2",_recorridos[2]);
    });

    socket.on("recorridos3", function(data3){
		socket.emit("recorridos3",_recorridos[3]);
    });

    socket.on("recorridos4", function(data4){
		socket.emit("recorridos4",_recorridos[4]);
    });

    socket.on("recorridos5", function(data5){
		socket.emit("recorridos5",_recorridos[5]);
    });

    socket.on("recorridos6", function(data6){
		socket.emit("recorridos6",_recorridos[6]);
    });

	socket.on("recorridos7", function(data7){
		socket.emit("recorridos7",_recorridos[7]);
    });

    socket.on("recorridos8", function(data8){
		socket.emit("recorridos8",_recorridos[8]);
    });

    socket.on("recorridos9", function(data9){
		socket.emit("recorridos9",_recorridos[9]);
    });

    socket.on("recorridos10", function(data10){
		socket.emit("recorridos10",_recorridos[10]);
    });

    socket.on("recorridos11", function(data11){
		socket.emit("recorridos11",_recorridos[11]);
    });

    socket.on("recorridos12", function(data12){

     socket.emit("recorridos12",_recorridos[12]);
    });

    socket.on("recorridos13", function(data13){

        socket.emit("recorridos13",_recorridos[13]);

    });

  });
//});

var d = new Date();
var h = d.getHours();

// Hora de de funcionamiento de las estaciones Plan Movete 7-21hs
//if ((h < 21) && (h >= 7))
//{
  io.sockets.on("connection", function(socket) {

    socket.on("paradas", function(data){
		request('http://movete.montevideo.gub.uy/index.php?option=com_content&view=article&id=1&Itemid=2', function (error, response, html) {
			if (!error && response.statusCode == 200) {
			  html = html.replace(/(\n|\r)/g,'');

			  /* Con esta exp reg obtengo solo el vector, índice 2 del array */

			  var re = /(var\s*paradas\s*=(\s*\[(.*?)\]));/;  //Funciona!!

			  var res = re.exec(html);  //obtenemos un string
			  // res[0] - todo el string var paradas = [..]
			  // res[2] - solo la parte entre [], los más externos

			  //saco [] más externos
			  var paradas1 = res[2].trim();
			  var paradas1 = paradas1.slice(0,res[2].length-1);  //desde 1 y mo 0

				if (paradas1.length > 0)
					{
						socket.emit('paradas', paradas1);
					}

			}
			else
				console.log("sin datos");

		  });  //request

	}); //socket paradas

  }); //socket connection

//} //if hora

 io.sockets.on('error', function() {
	  io.connect(host, {
		'force new connection': true
	  });
	 });

io.sockets.on('disconnect', function(){

});

 server.listen(process.env.PORT || 5000);
<|endoftext|>


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

/**
 * @fileoverview Externs file for the Iframes library.
 * @externs
 */

/**
 * The namespace for most of the APIs.
 *
 * This has "|undefined" so that JSCompiler won't optimize out existence checks
 * for win.iframes.
 *
 * @type {!iframes.Iframes|undefined}
 */
Window.prototype.iframes;

/**
 * The namespace for most of the APIs.
 * @type {!iframes.Iframes}
 */
var iframes;

/**
 * The type for the Iframes API.
 * @constructor
 */
iframes.Iframes = function() {};

/**
 * Gets the handler for a given style.
 *
 * @param {string} style
 * @return {Object|Function} The handler for the given style.
 */
iframes.Iframes.prototype.getHandler = function(style) {};

/**
 * Sets the handler for a given style.
 *
 * @param {string} style
 * @param {Object|Function} handler The handler for a given style.
 *     If the style handle is a function, the object returned by this
 *     function providing the iframe as the parameter is used as the
 *     actual style handler for every iframe opened in the style it
 *     handles.
 *     The follow methods of the handler are significant (note
 *     that the iframe parameter only presents when the original
 *     style handler is not a function):
 *       open(iframe): called to open the iframe.
 *         call iframe.openInto(el) to perform the open action.
 *       onready(iframe): called when the iframe is ready.
 *       close(iframe): called to close the iframe.
 */
iframes.Iframes.prototype.setHandler = function(style, handler) {};

/**
 * Gets a deferred-loaded style handler.
 * @param {string} style The name of the style.
 * @return {?function(function())} The deferred loader, if any.
 */
iframes.Iframes.prototype.getDeferredHandler = function(style) {};

/**
 * Sets a deferred-loaded style handler.
 * @param {string} style The name of the style.
 * @param {function(function())} loader The method to load the new style
 *     handler, which should call iframes.setHandler for that style.
 *     It then needs to call the callback method passed in.
 */
iframes.Iframes.prototype.setDeferredHandler = function(style, loader) {};

/**
 * This is an internal class to represent an iframe, not the DOM element.
 * @constructor
 */
iframes.Iframe = function() {};

/**
 * Methods used to pass to the iframe being opened.
 * @return {Object}
 */
iframes.Iframe.prototype.getMethods = function() {};

/**
 * Parameters used for opening the widget.
 * @return {Object}
 */
iframes.Iframe.prototype.getOpenParams = function() {};

/**
 * Parameters used to pass to the iframe being opened.
 * @return {Object}
 */
iframes.Iframe.prototype.getParams = function() {};

/**
 * @type {Element}
 * @deprecated
 */
iframes.Iframe.prototype.containerDiv;

/**
 * DOM reference to element containing iframe element.
 *
 * @return {Element}
 */
iframes.Iframe.prototype.getSiteEl = function() {};

/**
 * @param {Element} element DOM element containing the iframe element.
 */
iframes.Iframe.prototype.setSiteEl = function(element) {};

/**
 * DOM reference to the iframe element containing the widget.
 *
 * @return {Element}
 */
iframes.Iframe.prototype.getIframeEl = function() {};

/**
 * generated ID that will be set on the widget when it is opened
 *
 * @return {string}
 */
iframes.Iframe.prototype.getId = function() {};

/**
 * Iframe class instance that opened our Iframe instance.
 *
 * @return {iframes.Iframe}
 */
iframes.Iframe.prototype.getOpenerIframe = function() {};

/**
 * Exposes the method so it can be called from iframe object.
 * This is supposed be called by the style handler while handling 'open'
 * before calling 'openInto'.
 *
 * @param {string} name Name of the method as being called.
 * @param {Function} method The method to be exposed.
 */
iframes.Iframe.prototype.exposeMethod = function(name, method) {};

/**
 * @param {string|Element} el The DOM element or its ID to open the iframe
 *     into.
 * @param {Object=} opt_iframeAttributes Key-value pairs of iframe attributes.
 * @return {iframes.Iframe} This iframe object (not the DOM element).
 **/
iframes.Iframe.prototype.openInto = function(el, opt_iframeAttributes) {};

/**
 * Close the iframe.
 *
 * @param {*=} opt_params Data to pass to the callback.
 * @return {*} The result from the callback function.
 */
iframes.Iframe.prototype.close = function(opt_params) {};

/**
 * Removes the iframe element from the DOM tree.
 */
iframes.Iframe.prototype.remove = function() {};

/**
 * Adds a callback method for a given type of event.
 *
 * @param {string} type The event type: e.g. 'ready' or 'close'.
 * @param {Function} callback The callback method.
 */
iframes.Iframe.prototype.addCallback = function(type, callback) {};

/**
 * Removes a callback method for a given type of event.
 *
 * @param {string} type The event type used in 'addCallback'.
 * @param {Function} callback The callback method used in 'addCallback'.
 */
iframes.Iframe.prototype.removeCallback = function(type, callback) {};

/**
 * Allows a global function for all iframes.
 *
 * @param {string} name The name of the method for the iframes.
 * @param {?function(...[*]) : *=} opt_func A optional function, by default
 *     window[name].
 */
iframes.Iframes.prototype.allow = function(name, opt_func) {};

/**
 * Opens an iframe.
 *
 * WARNING: This api takes ownership of all object parameters and will
 * modify them. Make a copy if you want to reuse them.
 *
 * @param {string} url The URL of the iframe to be opened.
 * @param {Object} openParams The parameters for opening the iframe.
 *    style: specify which handler is used.
 *    For the default handler, the follow attributes are used:
 *      element: the element which the iframe would be opened into.
 * @param {Object} params The data to be passed to the iframe.
 *     All properties should be string.
 * @param {Object|function(...[*]) : *=} opt_methods: Functions to passed to
 *     the iframe. All properties should be functions. If no 'callback'
 *     argument is provided and the argument in this position is a function
 *     instead of an object, it is considered as the  next parameter
 *     'callback', not this parameter 'methods'.
 * @param {?function(...[*]) : *=} opt_callback: a callback function called
 *     when the iframe is closed.
 *
 * @return {iframes.Iframe} The opened iframe.
 */
iframes.Iframes.prototype.open = function(
    url, openParams, params, opt_methods, opt_callback) {};

/**
 * Closes this iframe.
 *
 * @param {*=} opt_params The parameters to pass back to the parent iframe.
 * @param {?function(...[*]) : *=} opt_callback The callback function after
 *     parent processed the event.
 */
iframes.Iframes.prototype.close = function(opt_params, opt_callback) {};

/**
 * Indicates that this iframe is ready.  The exactly semantic depends on
 * the style handler, but generally it means ready to be displayed and
 * it should be called the page is drawn and initial data loaded.
 *
 * @param {Object=} opt_params The parameters to pass back to the parent iframe.
 * @param {Object|function(...[*]) : *=} opt_methods: Functions to passed to
 *     the iframe. All properties should be functions. If no 'callback'
 *     argument is provided and the argument in this position is a function
 *     instead of an object, it is considered as the  next parameter
 *     'callback', not this parameter 'methods'.
 * @param {?function(...[*]) : *=} opt_callback The callback function after
 *     parent processed the event.
 */
iframes.Iframes.prototype.ready = function(
    opt_params, opt_methods, opt_callback) {};

/**
 * Passes the parent's origin and referer to the callback.
 * @param {Function} callback Function
 *     that will get parent info passed to it.
 */
iframes.Iframes.prototype.getParentInfo = function(callback) {};

/**
 * Export browser events to your opener.
 * @param {Array.<string>} events List of events to export. Currently only
 *     supports mouseover and mouseout.
 */
iframes.Iframes.prototype.propagate = function(events) {};

/**
 * Asks the parent window to change the width and height of this iframe.
 *
 * @param {Object} params The width and/or height in number of pixels to be
 *     resized. Use 'height': 'auto' for current window content height.
 */

iframes.Iframes.prototype.resize = function(params) {};

/**
 * @param {Object} params
 * @deprecated
 */
iframes.Iframes.prototype.resizeMe = function(params) {};

/**
 * @return {string} The full URI for the Google Connect JS bundle.
 */
iframes.Iframes.prototype.getGoogleConnectJsUri = function() {};

/**
 * Allows client to override the Google Connect JS to use.
 * @param {string} version The versioned JS file to use.
 */
iframes.Iframes.prototype.setGoogleConnectJsVersion = function(version) {};

/**
 * Allows the client to use a different JS hint.
 * @param {string} hint The JS hint to use.
 */
iframes.Iframes.prototype.setJsHint = function(hint) {};

/**
 * Allows the client to use a different bootstrap hint.
 * @param {string} hint The boostrap hint to use.
 */
iframes.Iframes.prototype.setBootstrapHint = function(hint) {};

/**
 * @param {string} version
 * @deprecated
 */
iframes.Iframes.prototype.setVersionOverride = function(version) {};

/**
 * Inside an iframe, the properties of the iframer object are the data and
 * functions provided in iframes.open.
 * @type {!Object|undefined}
 */
Window.prototype.iframer = {};

/**
 * Inside an iframe, the properties of the iframer object are the data and
 * functions provided in iframes.open.
 * @type {!Object|undefined}
 */
iframes.Iframes.prototype.iframer = {};

/**
 * Inside an iframe, the properties of the iframer object are the data and
 * functions provided in iframes.open.
 * @type {!Object}
 */
var iframer = {};
<commit_msg><commit_after>/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Externs file for the Iframes library.
 * @externs
 */

/**
 * The namespace for most of the APIs.
 */
window.iframes = {};
var iframes = {};

/**
 * Gets the handler for a given style.
 *
 * @param {string} style
 * @return {Object|Function} The handler for the given style.
 */
iframes.getHandler = function(style) {};

/**
 * Sets the handler for a given style.
 *
 * @param {string} style
 * @param {Object|Function} handler The handler for a given style.
 *     If the style handle is a function, the object returned by this
 *     function providing the iframe as the parameter is used as the
 *     actual style handler for every iframe opened in the style it
 *     handles.
 *     The follow methods of the handler are significant (note
 *     that the iframe parameter only presents when the original
 *     style handler is not a function):
 *       open(iframe): called to open the iframe.
 *         call iframe.openInto(el) to perform the open action.
 *       onready(iframe): called when the iframe is ready.
 *       close(iframe): called to close the iframe.
 */
iframes.setHandler = function(style, handler) {};

/**
 * Gets a deferred-loaded style handler.
 * @param {string} style The name of the style.
 * @return {?function(function())} The deferred loader, if any.
 */
iframes.getDeferredHandler = function(style) {};

/**
 * Sets a deferred-loaded style handler.
 * @param {string} style The name of the style.
 * @param {function(function())} loader The method to load the new style
 *     handler, which should call iframes.setHandler for that style.
 *     It then needs to call the callback method passed in.
 */
iframes.setDeferredHandler = function(style, loader) {};

/**
 * This is an internal class to represent an iframe, not the DOM element.
 * @constructor
 */
iframes.Iframe = function() {};

/**
 * Methods used to pass to the iframe being opened.
 * @return {Object}
 */
iframes.Iframe.prototype.getMethods = function() {};

/**
 * Parameters used for opening the widget.
 * @return {Object}
 */
iframes.Iframe.prototype.getOpenParams = function() {};

/**
 * Parameters used to pass to the iframe being opened.
 * @return {Object}
 */
iframes.Iframe.prototype.getParams = function() {};

/**
 * @type {Element}
 * @deprecated
 */
iframes.Iframe.prototype.containerDiv;

/**
 * DOM reference to element containing iframe element.
 *
 * @return {Element}
 */
iframes.Iframe.prototype.getSiteEl = function() {};

/**
 * @param {Element} element DOM element containing the iframe element.
 */
iframes.Iframe.prototype.setSiteEl = function(element) {};

/**
 * DOM reference to the iframe element containing the widget.
 *
 * @return {Element}
 */
iframes.Iframe.prototype.getIframeEl = function() {};

/**
 * generated ID that will be set on the widget when it is opened
 *
 * @return {string}
 */
iframes.Iframe.prototype.getId = function() {};

/**
 * Iframe class instance that opened our Iframe instance.
 *
 * @return {iframes.Iframe}
 */
iframes.Iframe.prototype.getOpenerIframe = function() {};

/**
 * Exposes the method so it can be called from iframe object.
 * This is supposed be called by the style handler while handling 'open'
 * before calling 'openInto'.
 *
 * @param {string} name Name of the method as being called.
 * @param {Function} method The method to be exposed.
 */
iframes.Iframe.prototype.exposeMethod = function(name, method) {};

/**
 * @param {string|Element} el The DOM element or its ID to open the iframe
 *     into.
 * @param {Object=} opt_iframeAttributes Key-value pairs of iframe attributes.
 * @return {iframes.Iframe} This iframe object (not the DOM element).
 **/
iframes.Iframe.prototype.openInto = function(el, opt_iframeAttributes) {};

/**
 * Close the iframe.
 *
 * @param {*=} opt_params Data to pass to the callback.
 * @return {*} The result from the callback function.
 */
iframes.Iframe.prototype.close = function(opt_params) {};

/**
 * Removes the iframe element from the DOM tree.
 */
iframes.Iframe.prototype.remove = function() {};

/**
 * Adds a callback method for a given type of event.
 *
 * @param {string} type The event type: e.g. 'ready' or 'close'.
 * @param {Function} callback The callback method.
 */
iframes.Iframe.prototype.addCallback = function(type, callback) {};

/**
 * Removes a callback method for a given type of event.
 *
 * @param {string} type The event type used in 'addCallback'.
 * @param {Function} callback The callback method used in 'addCallback'.
 */
iframes.Iframe.prototype.removeCallback = function(type, callback) {};

/**
 * Allows a global function for all iframes.
 *
 * @param {string} name The name of the method for the iframes.
 * @param {?function(...[*]) : *=} opt_func A optional function, by default
 *     window[name].
 */
iframes.allow = function(name, opt_func) {};

/**
 * Opens an iframe.
 *
 * WARNING: This api takes ownership of all object parameters and will
 * modify them. Make a copy if you want to reuse them.
 *
 * @param {string} url The URL of the iframe to be opened.
 * @param {Object} openParams The parameters for opening the iframe.
 *    style: specify which handler is used.
 *    For the default handler, the follow attributes are used:
 *      element: the element which the iframe would be opened into.
 * @param {Object} params The data to be passed to the iframe.
 *     All properties should be string.
 * @param {Object|function(...[*]) : *=} opt_methods: Functions to passed to
 *     the iframe. All properties should be functions. If no 'callback'
 *     argument is provided and the argument in this position is a function
 *     instead of an object, it is considered as the  next parameter
 *     'callback', not this parameter 'methods'.
 * @param {?function(...[*]) : *=} opt_callback: a callback function called
 *     when the iframe is closed.
 *
 * @return {iframes.Iframe} The opened iframe.
 */
iframes.open = function(url, openParams, params, opt_methods, opt_callback) {};

/**
 * Closes this iframe.
 *
 * @param {*=} opt_params The parameters to pass back to the parent iframe.
 * @param {?function(...[*]) : *=} opt_callback The callback function after
 *     parent processed the event.
 */
iframes.close = function(opt_params, opt_callback) {};

/**
 * Indicates that this iframe is ready.  The exactly semantic depends on
 * the style handler, but generally it means ready to be displayed and
 * it should be called the page is drawn and initial data loaded.
 *
 * @param {Object=} opt_params The parameters to pass back to the parent iframe.
 * @param {Object|function(...[*]) : *=} opt_methods: Functions to passed to
 *     the iframe. All properties should be functions. If no 'callback'
 *     argument is provided and the argument in this position is a function
 *     instead of an object, it is considered as the  next parameter
 *     'callback', not this parameter 'methods'.
 * @param {?function(...[*]) : *=} opt_callback The callback function after
 *     parent processed the event.
 */
iframes.ready = function(opt_params, opt_methods, opt_callback) {};

/**
 * Passes the parent's origin and referer to the callback.
 * @param {Function} callback Function
 *     that will get parent info passed to it.
 */
iframes.getParentInfo = function(callback) {};

/**
 * Export browser events to your opener.
 * @param {Array.<string>} events List of events to export. Currently only
 *     supports mouseover and mouseout.
 */
iframes.propagate = function(events) {};

/**
 * Asks the parent window to change the width and height of this iframe.
 *
 * @param {Object} params The width and/or height in number of pixels to be
 *     resized. Use 'height': 'auto' for current window content height.
 */

iframes.resize = function(params) {};

/**
 * @param {Object} params
 * @deprecated
 */
iframes.resizeMe = function(params) {};

/**
 * @return {string} The full URI for the Google Connect JS bundle.
 */
iframes.getGoogleConnectJsUri = function() {};

/**
 * Allows client to override the Google Connect JS to use.
 * @param {string} version The versioned JS file to use.
 */
iframes.setGoogleConnectJsVersion = function(version) {};

/**
 * Allows the client to use a different JS hint.
 * @param {string} hint The JS hint to use.
 */
iframes.setJsHint = function(hint) {};

/**
 * Allows the client to use a different bootstrap hint.
 * @param {string} hint The boostrap hint to use.
 */
iframes.setBootstrapHint = function(hint) {};

/**
 * @param {string} version
 * @deprecated
 */
iframes.setVersionOverride = function(version) {};

/**
 * Inside an iframe, the properties of the iframer object are the data and
 * functions provided in iframes.open.
 */
window.iframer = {};
var iframer = {};
<|endoftext|>


<commit_before>import React from 'react'
import { connect } from 'react-redux'

import Pipeline from 'sajari-react/pipeline/Pipeline'
import Value from 'sajari-react/pipeline/Value'
import Response from 'sajari-react/pipeline/Response'
import { RegisterNamespace } from 'sajari-react/api'
import { PipelineSummary, Results } from 'sajari-react/ui/Results'
import { makePipelineSearchRequest } from 'sajari-react/api/actions/pipeline'
import PipelineInput from 'sajari-react/pipeline/PipelineInput'

import './styles.css'

class app extends React.Component {
  componentDidMount() {
    if (!this.props.config.searchBox) {
      // If the search box isn't active, perform a search once the component has mounted
      // This is for when there is a query param in the url and we just want to show the results
      this.props.search()
    }
  }

  render() {
    const { project, collection, pipeline, values, searchBox, searchBoxPlaceHolder } = this.props.config

    // Don't render Value of q if the search box is active. It will handle the q value.
    const isNotQ = v => searchBox ? v !== 'q' : v

    // Render the list of values
    const renderedValues = Object.keys(values).filter(isNotQ).map(k => (
      <Value key={k} pipeline={pipeline} name={k} value={values[k]}/>
    ))

    return (

        <RegisterNamespace project={project} collection={collection}/>
        <Pipeline name={pipeline}/>
        {searchBox ? <PipelineInput pipeline={pipeline} initialValue={values.q} placeHolder={searchBoxPlaceHolder} /> : null}
        {renderedValues}
        <Response pipeline={pipeline}>
          <PipelineSummary pipeline={pipeline} />
          <Results/>
        </Response>

    )
  }
}

const App = connect(
  null,
  (dispatch, props) => ({
    search: () => dispatch(makePipelineSearchRequest('default', props.config.pipeline))
  })
)(app)

export default App
<commit_msg>Add pagination to site search integration example<commit_after>import React from 'react'
import { connect } from 'react-redux'

import Pipeline from 'sajari-react/pipeline/Pipeline'
import Value from 'sajari-react/pipeline/Value'
import Response from 'sajari-react/pipeline/Response'
import { RegisterNamespace } from 'sajari-react/api'
import { PipelineSummary, Results } from 'sajari-react/ui/Results'
import { makePipelineSearchRequest } from 'sajari-react/api/actions/pipeline'
import PipelineInput from 'sajari-react/pipeline/PipelineInput'
import { PipelinePaginator as Paginator } from 'sajari-react/ui/Paginator'

import './styles.css'

class app extends React.Component {
  componentDidMount() {
    if (!this.props.config.searchBox) {
      // If the search box isn't active, perform a search once the component has mounted
      // This is for when there is a query param in the url and we just want to show the results
      this.props.search()
    }
  }

  render() {
    const { project, collection, pipeline, values, searchBox, searchBoxPlaceHolder } = this.props.config

    // Don't render Value of q if the search box is active. It will handle the q value.
    const isNotQ = v => searchBox ? v !== 'q' : v

    // Render the list of values
    const renderedValues = Object.keys(values).filter(isNotQ).map(k => (
      <Value key={k} pipeline={pipeline} name={k} value={values[k]}/>
    ))

    return (

        <RegisterNamespace project={project} collection={collection}/>
        <Pipeline name={pipeline}/>
        {searchBox ? <PipelineInput pipeline={pipeline} initialValue={values.q} placeHolder={searchBoxPlaceHolder} /> : null}
        {renderedValues}
        <Response pipeline={pipeline}>
          <PipelineSummary pipeline={pipeline} />
          <Results/>
          <Paginator pipeline={pipeline} />
        </Response>

    )
  }
}

const App = connect(
  null,
  (dispatch, props) => ({
    search: () => dispatch(makePipelineSearchRequest('default', props.config.pipeline))
  })
)(app)

export default App
<|endoftext|>


<commit_before>/*
 * @author     Martin Høgh <mh@mapcentia.com>
 * @copyright  2013-2018 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

module.exports = {

    // ===============================================
    // Following settings are set under build/startup.
    // They can not be altered in run-time.
    // ===============================================

    // ========================================
    // Which back-end is used. "gc2" or "carto"
    // ONLY GC2 IS SUPPORTED
    // ========================================

    "backend": "gc2",

    // ==========================================
    // GC2 host. Both HTTP and HTTPS is supported
    // Carto is hardcoded to carto.com
    // ==========================================

    "gc2": {
        "host": "GC2_HOST"
    },

    // ===============================================================
    // Configuration of print templates.
    // Print templates must be enabled - look further down.
    // mapsizePx ~ is the pixel dimension of the map view
    // mapsizeMm ~ is actual size of the map view on the resulting PDF
    // ===============================================================

    "print": {
        "templates": {

            // ================================
            // The "print" template is build-in
            // ================================

            "print": {
                A4: {
                    l: {
                        mapsizePx: [1060, 730],
                        mapsizeMm: [280, 167]
                    },
                    p: {
                        mapsizePx: [730, 1060],
                        mapsizeMm: [167, 280]
                    }
                },
                A3: {
                    l: {
                        mapsizePx: [1525, 1065],
                        mapsizeMm: [403, 255]
                    },
                    p: {
                        mapsizePx: [1065, 1525],
                        mapsizeMm: [255, 403]
                    }
                },
                A2: {
                    l: {
                        mapsizePx: [2185, 1525],
                        mapsizeMm: [578, 376]
                    },
                    p: {
                        mapsizePx: [1525, 2185],
                        mapsizeMm: [376, 578]
                    }
                },
                A1: {
                    l: {
                        mapsizePx: [3120, 2185],
                        mapsizeMm: [826, 552]
                    },
                    p: {
                        mapsizePx: [2185, 3120],
                        mapsizeMm: [552, 826]
                    }
                },
                A0: {
                    l: {
                        mapsizePx: [4430, 3120],
                        mapsizeMm: [1172, 800]
                    },
                    p: {
                        mapsizePx: [3120, 4430],
                        mapsizeMm: [800, 1173]
                    }
                }
            },

            // ========================================================
            // Names starting with "_" will not appear in the Print tab
            // But can be used by other extensions.
            // Must not be enabled.
            // ========================================================

            "_conflictPrint": {
                "A4": {
                    "p": {
                        "mapsizePx": [700, 500],
                        "mapsizeMm": [190, 120]
                    }
                }
            }
        },

        // =====================
        // Pre-configured scales
        // =====================

        "scales": [100, 250, 500, 1000, 2000, 3000, 4000, 5000, 7500, 10000, 15000, 25000, 50000, 100000]
    },

    // =======================================================
    // Extensions are required in build-time, but not enabled.
    // Look further down about enabling of extensions
    // Extensions can be required both browser and server side
    // =======================================================

    "extensions": {
        "browser": [
            {"conflictSearch": ["index", "reportRender", "infoClick", "controller"]},
            {"streetView": ["index"]},
            {"coordinates": ["index"]},
            {"offlineMap": ["index"]},
            {"session": ["index"]},
            {"editor": ["index"]},

        ],
        "server": [
            {"conflictSearch": ["index"]},
            {"session": ["index"]},
        ]
    },

    // ===========================================================
    // Search modules are required in build-time, but not enabled.
    // Look further down about enabling search modules
    // ===========================================================

    "searchModules": ["google", "danish"],

    //=============================================================================
    // An URL (or URLs) where configurations can be downloaded online for
    // overriding build-time configurations. Any HTTP(S) server can host config files
    // Example of a config:
    // https://github.com/mapcentia/mapcentia.github.io/blob/master/vidi.json
    // Can be used by adding ?config=vidi.json to the Vidi URL'en
    //=============================================================================

    "configUrl": "https://mapcentia.github.io",

    // Database depend URLs

    //"configUrl": {
    //    "a_database": "https://mapcentia.github.io",
    //    "an_other_database": "https://example.github.io",
    //    "_default": "https://fall_back.github.io"
    //},

    // ==========================================================
    // With auto loading of configurations, Vidi will try to load
    // a config json file with the same name as the user name
    // (Database name in GC2 and account name in Carto)
    // ==========================================================

    "autoLoadingConfig": false,

    // ====================================================================================
    // A fallback config file. If no config is given in the URL or auto loading is disabled
    // OR URL / auto loading is returning a 404 or a invalid jSON file
    // ====================================================================================

    "defaultConfig": "vidi.json",

    // =============================================================
    // Below are configurations, which can be overridden in run-time
    // These can be placed in a online config JSON file
    // hosted at the "configUrl" setting
    // =============================================================

    // ===================================
    // Brand name for the default template
    // ===================================

    "brandName": "MapCentia ApS",

    // ===================================
    // About text for the default template
    // ===================================

    "aboutBox": "
My awesome web map
",

    // ===================================================
    // Enabling of extensions
    // Extensions are enabled both browser and server side
    // ===================================================

    "enabledExtensions": [
        "conflictSearch",
        "streetView",
        "session",
        "coordinates",
        "offlineMap",
        "editor",
    ],

    // ====================================
    // Set which template Vidi should use
    // default.tmpl is the build-in default
    // ====================================

    "template": "default.tmpl",

    // ===========================
    // Enabling of print templates
    // ===========================

    "enabledPrints": ["print"],

    // =====================================
    // Enabling of a search module
    // Only one search module can be enabled
    // =====================================

    "enabledSearch": "google",

    // =================================
    // Configurations for search modules
    // =================================

    "searchConfig": {
        "komkode": ["851", "151"] // Example of config for danish search
    },

    // =========================================================
    // Configurations for extension modules
    // An extension can read the whole config, but by convention,
    // its a good idea to put them inside this property
    // ==========================================================

    "extensionConfig": {
        "layerSearch": {
            "host": "localhost:9200"
        }
    },

    // ===================================================
    // Configuration of which base layers are available
    //
    // GC2 has the following pre-defined base layers
    //
    // osm
    // stamenToner
    // stamenTonerLite
    // googleStreets
    // googleHybrid
    // googleSatellite
    // dtkSkaermkort
    // dtkSkaermkortDaempet
    // hereNormalNightGrey
    // ===================================================

    "baseLayers": [

        {
            "id": "geodk.bright",
            "name": "GeoDanmark kort",
            "db": "baselayers",
            "host": "https://gc2.io",
            "config": {
                "maxZoom": 21,
                "maxNativeZoom": 19,
                "attribution": "© SDFE & MapCentia ApS"
            }
        },

        // Pre-defined base layers
        {"id": "osm", "name": "Open Street Map"},

        {"id": "stamenTonerLite", "name": "Stamen Toner Light"},

        // Base layer from GC2
        {
            "id": "gc2_group._b_baggrundskort01.baggrundskort01",
            "name": "Topografisk kort",
            "db": "geofyn",
            "host": "https://kort.geofyn.dk",
            "config": {
                "maxZoom": 21,
                "maxNativeZoom": 19,
                "attribution": "Geofyn A/S"
            },

            // A base layer can comprise one or more overlays
            "overlays": [
                {
                    "id": "tekster.tekster_samlet_wms_web",
                    "db": "geofyn",
                    "host": "https://kort.geofyn.dk",
                    "config": {
                        "attribution": "Geofyn A/S"
                    }
                },
                {
                    "id": "tekster.adgangsadresseinfo",
                    "db": "geofyn",
                    "host": "https://kort.geofyn.dk",
                    "config": {
                        "attribution": "Geofyn A/S"
                    }
                }]
        }
    ]
};
<commit_msg>No default config<commit_after>/*
 * @author     Martin Høgh <mh@mapcentia.com>
 * @copyright  2013-2018 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

module.exports = {

    // ===============================================
    // Following settings are set under build/startup.
    // They can not be altered in run-time.
    // ===============================================

    // ========================================
    // Which back-end is used. "gc2" or "carto"
    // ONLY GC2 IS SUPPORTED
    // ========================================

    "backend": "gc2",

    // ==========================================
    // GC2 host. Both HTTP and HTTPS is supported
    // Carto is hardcoded to carto.com
    // ==========================================

    "gc2": {
        "host": "GC2_HOST"
    },

    // ===============================================================
    // Configuration of print templates.
    // Print templates must be enabled - look further down.
    // mapsizePx ~ is the pixel dimension of the map view
    // mapsizeMm ~ is actual size of the map view on the resulting PDF
    // ===============================================================

    "print": {
        "templates": {

            // ================================
            // The "print" template is build-in
            // ================================

            "print": {
                A4: {
                    l: {
                        mapsizePx: [1060, 730],
                        mapsizeMm: [280, 167]
                    },
                    p: {
                        mapsizePx: [730, 1060],
                        mapsizeMm: [167, 280]
                    }
                },
                A3: {
                    l: {
                        mapsizePx: [1525, 1065],
                        mapsizeMm: [403, 255]
                    },
                    p: {
                        mapsizePx: [1065, 1525],
                        mapsizeMm: [255, 403]
                    }
                },
                A2: {
                    l: {
                        mapsizePx: [2185, 1525],
                        mapsizeMm: [578, 376]
                    },
                    p: {
                        mapsizePx: [1525, 2185],
                        mapsizeMm: [376, 578]
                    }
                },
                A1: {
                    l: {
                        mapsizePx: [3120, 2185],
                        mapsizeMm: [826, 552]
                    },
                    p: {
                        mapsizePx: [2185, 3120],
                        mapsizeMm: [552, 826]
                    }
                },
                A0: {
                    l: {
                        mapsizePx: [4430, 3120],
                        mapsizeMm: [1172, 800]
                    },
                    p: {
                        mapsizePx: [3120, 4430],
                        mapsizeMm: [800, 1173]
                    }
                }
            },

            // ========================================================
            // Names starting with "_" will not appear in the Print tab
            // But can be used by other extensions.
            // Must not be enabled.
            // ========================================================

            "_conflictPrint": {
                "A4": {
                    "p": {
                        "mapsizePx": [700, 500],
                        "mapsizeMm": [190, 120]
                    }
                }
            }
        },

        // =====================
        // Pre-configured scales
        // =====================

        "scales": [100, 250, 500, 1000, 2000, 3000, 4000, 5000, 7500, 10000, 15000, 25000, 50000, 100000]
    },

    // =======================================================
    // Extensions are required in build-time, but not enabled.
    // Look further down about enabling of extensions
    // Extensions can be required both browser and server side
    // =======================================================

    "extensions": {
        "browser": [
            {"conflictSearch": ["index", "reportRender", "infoClick", "controller"]},
            {"streetView": ["index"]},
            {"coordinates": ["index"]},
            {"offlineMap": ["index"]},
            {"session": ["index"]},
            {"editor": ["index"]},

        ],
        "server": [
            {"conflictSearch": ["index"]},
            {"session": ["index"]},
        ]
    },

    // ===========================================================
    // Search modules are required in build-time, but not enabled.
    // Look further down about enabling search modules
    // ===========================================================

    "searchModules": ["google", "danish"],

    //=============================================================================
    // An URL (or URLs) where configurations can be downloaded online for
    // overriding build-time configurations. Any HTTP(S) server can host config files
    // Example of a config:
    // https://github.com/mapcentia/mapcentia.github.io/blob/master/vidi.json
    // Can be used by adding ?config=vidi.json to the Vidi URL'en
    //=============================================================================

    "configUrl": "https://mapcentia.github.io",

    // Database depend URLs

    //"configUrl": {
    //    "a_database": "https://mapcentia.github.io",
    //    "an_other_database": "https://example.github.io",
    //    "_default": "https://fall_back.github.io"
    //},

    // ==========================================================
    // With auto loading of configurations, Vidi will try to load
    // a config json file with the same name as the user name
    // (Database name in GC2 and account name in Carto)
    // ==========================================================

    "autoLoadingConfig": false,

    // ====================================================================================
    // A fallback config file. If no config is given in the URL or auto loading is disabled
    // OR URL / auto loading is returning a 404 or a invalid jSON file
    // ====================================================================================

    //"defaultConfig": "vidi.json",

    // =============================================================
    // Below are configurations, which can be overridden in run-time
    // These can be placed in a online config JSON file
    // hosted at the "configUrl" setting
    // =============================================================

    // ===================================
    // Brand name for the default template
    // ===================================

    "brandName": "MapCentia ApS",

    // ===================================
    // About text for the default template
    // ===================================

    "aboutBox": "
My awesome web map
",

    // ===================================================
    // Enabling of extensions
    // Extensions are enabled both browser and server side
    // ===================================================

    "enabledExtensions": [
        "conflictSearch",
        "streetView",
        "session",
        "coordinates",
        "offlineMap",
        "editor",
    ],

    // ====================================
    // Set which template Vidi should use
    // default.tmpl is the build-in default
    // ====================================

    "template": "default.tmpl",

    // ===========================
    // Enabling of print templates
    // ===========================

    "enabledPrints": ["print"],

    // =====================================
    // Enabling of a search module
    // Only one search module can be enabled
    // =====================================

    "enabledSearch": "google",

    // =================================
    // Configurations for search modules
    // =================================

    "searchConfig": {
        "komkode": ["851", "151"] // Example of config for danish search
    },

    // =========================================================
    // Configurations for extension modules
    // An extension can read the whole config, but by convention,
    // its a good idea to put them inside this property
    // ==========================================================

    "extensionConfig": {
        "layerSearch": {
            "host": "localhost:9200"
        }
    },

    // ===================================================
    // Configuration of which base layers are available
    //
    // GC2 has the following pre-defined base layers
    //
    // osm
    // stamenToner
    // stamenTonerLite
    // googleStreets
    // googleHybrid
    // googleSatellite
    // dtkSkaermkort
    // dtkSkaermkortDaempet
    // hereNormalNightGrey
    // ===================================================

    "baseLayers": [

        {
            "id": "geodk.bright",
            "name": "GeoDanmark kort",
            "db": "baselayers",
            "host": "https://gc2.io",
            "config": {
                "maxZoom": 21,
                "maxNativeZoom": 19,
                "attribution": "© SDFE & MapCentia ApS"
            }
        },

        // Pre-defined base layers
        {"id": "osm", "name": "Open Street Map"},

        {"id": "stamenTonerLite", "name": "Stamen Toner Light"},

        // Base layer from GC2
        {
            "id": "gc2_group._b_baggrundskort01.baggrundskort01",
            "name": "Topografisk kort",
            "db": "geofyn",
            "host": "https://kort.geofyn.dk",
            "config": {
                "maxZoom": 21,
                "maxNativeZoom": 19,
                "attribution": "Geofyn A/S"
            },

            // A base layer can comprise one or more overlays
            "overlays": [
                {
                    "id": "tekster.tekster_samlet_wms_web",
                    "db": "geofyn",
                    "host": "https://kort.geofyn.dk",
                    "config": {
                        "attribution": "Geofyn A/S"
                    }
                },
                {
                    "id": "tekster.adgangsadresseinfo",
                    "db": "geofyn",
                    "host": "https://kort.geofyn.dk",
                    "config": {
                        "attribution": "Geofyn A/S"
                    }
                }]
        }
    ]
};
<|endoftext|>


<commit_before>"use strict";

const extend = require('extend');
const HyperSwitch = require('hyperswitch');
const Template = HyperSwitch.Template;
const utils = require('../lib/utils');
const Title = require('mediawiki-title').Title;

const BACKLINKS_CONTINUE_TOPIC_NAME = 'change-prop.backlinks.continue';
const TRANSCLUDES_CONTINUE_TOPIC_NAME = 'change-prop.transcludes.continue';

function createBackLinksTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                list: 'backlinks',
                bltitle: '{{request.params.title}}',
                blfilterredir: 'nonredirects',
                blcontinue: '{{message.continue}}',
                bllimit: 500
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.blcontinue,
        continueTopic: BACKLINKS_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'backlinks',
        extractResults: (res) => res.body.query.backlinks
    };
}

function createImageUsageTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                list: 'imageusage',
                iutitle: '{{request.params.title}}',
                iucontinue: '{{message.continue}}',
                iulimit: 500
                // TODO: decide what do we want to do with redirects
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.iucontinue,
        continueTopic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'transcludes',
        extractResults: (res) =>  res.body.query.imageusage
    };
}

function createTranscludeInTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                prop: 'transcludedin',
                tiprop: 'title',
                tishow: '!redirect',
                titles: '{{request.params.title}}',
                ticontinue: '{{message.continue}}',
                tilimit: 500
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.ticontinue,
        continueTopic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'transcludes',
        extractResults: (res) => {
            return res.body.query.pages[Object.keys(res.body.query.pages)[0]].transcludedin;
        }
    };
}

function createWikidataTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'wbgetentities',
                ids: '{{message.page_title}}',
                props: 'sitelinks/urls',
                normalize: true
            }
        })),
        resourceChangeTag: 'wikidata',
        shouldProcess: (res) => {
            if (!(res && res.body && !!res.body.success)) {
                return false;
            }
            const entities = res.body.entities || {};
            if (!Object.keys(entities).length) {
                return false;
            }
            const sitelinks = entities[Object.keys(entities)[0]].sitelinks || {};
            if (!Object.keys(sitelinks).length) {
                return false;
            }
            return true;
        },
        extractResults: (res) => {
            const siteLinks = res.body.entities[Object.keys(res.body.entities)[0]].sitelinks;
            return Object.keys(siteLinks).map((siteId) => {
                const pageURI = siteLinks[siteId].url;
                const match = /^https?:\/\/([^\/]+)\/wiki\/(.+)$/.exec(pageURI);
                return {
                    domain: match[1],
                    title: decodeURIComponent(match[2])
                };
            });
        }
    };
}

class DependencyProcessor {
    constructor(options) {
        this.options = options;
        this.log = options.log || function() { };
        this.siteInfoCache = {};
        this.backLinksRequest = createBackLinksTemplate(options);
        this.imageLinksRequest = createImageUsageTemplate(options);
        this.transcludeInRequest = createTranscludeInTemplate(options);
        this.wikidataRequest = createWikidataTemplate(options);
        this.siteInfoRequest = new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                meta: 'siteinfo',
                siprop: 'general|namespaces|namespacealiases'
            }
        }));
    }

    setup(hyper) {
        return hyper.post({
            uri: '/sys/queue/subscriptions',
            body: {
                backlinks_continue: {
                    topic: BACKLINKS_CONTINUE_TOPIC_NAME,
                    exec: [
                        {
                            method: 'post',
                            uri: '/sys/links/backlinks/{message.original_event.page_title}',
                            body: '{{globals.message}}'
                        }
                    ]
                },
                transclusions_continue: {
                    topic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
                    exec: [
                        {
                            method: 'post',
                            uri: '/sys/links/transcludes/{message.original_event.page_title}',
                            body: '{{globals.message}}'
                        }
                    ]
                }
            }
        });
    }

    processBackLinks(hyper, req) {
        const context = {
            request: req,
            message: req.body
        };
        const originalEvent = req.body.original_event || req.body;
        return this._getSiteInfo(hyper, req.body)
        .then((siteInfo) => {
            return this._fetchAndProcessBatch(hyper, this.backLinksRequest,
                context, siteInfo, originalEvent);
        });
    }

    processTranscludes(hyper, req) {
        const message = req.body;
        const context = {
            request: req,
            message: message
        };
        const originalEvent = req.body.original_event || req.body;
        return this._getSiteInfo(hyper, message)
        .then((siteInfo) => {
            const title = Title.newFromText(req.params.title, siteInfo);
            if (title.getNamespace().isFile()) {
                return this._fetchAndProcessBatch(hyper, this.imageLinksRequest,
                    context, siteInfo, originalEvent);
            }
            return this._fetchAndProcessBatch(hyper, this.transcludeInRequest,
                context, siteInfo, originalEvent);
        });
    }

    processWikidata(hyper, req) {
        const context = {
            request: req,
            message: req.body
        };
        return hyper.request(this.wikidataRequest.template.expand(context))
        .then((res) => {
            if (this.wikidataRequest.shouldProcess(res)) {
                const items = this.wikidataRequest.extractResults(res);
                return this._sendResourceChanges(hyper, items, req.body,
                    this.wikidataRequest.resourceChangeTag);
            } else if (res.body && res.body.error) {
                this.log('warn/wikidata_description', {
                    msg: 'Could not extract items',
                    event: context.message,
                    error: res.body.error
                });
            }
        })
        .thenReturn({ status: 200 });
    }

    _fetchAndProcessBatch(hyper, requestTemplate, context, siteInfo, originalEvent) {
        return hyper.post(requestTemplate.template.expand(context))
        .then((res) => {
            const titles = (requestTemplate.extractResults(res) || []).map((item) => {
                return {
                    title: Title.newFromText(item.title, siteInfo).getPrefixedDBKey(),
                    domain: originalEvent.meta.domain
                };
            });
            if (!titles.length) {
                // the batch is complete or the list of transcluded titles is empty
                return { status: 200 };
            }
            let actions = this._sendResourceChanges(hyper, titles,
                originalEvent, requestTemplate.resourceChangeTag);
            if (res.body.continue) {
                actions = actions.then(() =>
                    this._sendContinueEvent(hyper,
                        requestTemplate.continueTopic,
                        originalEvent,
                        requestTemplate.getContinueToken(res)));
            }
            return actions.thenReturn({ status: 200 });
        });
    }

    _sendContinueEvent(hyper, topic, origEvent, continueToken) {
        return hyper.post({
            uri: '/sys/queue/events',
            body: [{
                meta: {
                    topic: topic,
                    schema_uri: 'continue/1',
                    uri: origEvent.meta.uri,
                    request_id: origEvent.meta.request_id,
                    domain: origEvent.meta.domain,
                    dt: origEvent.meta.dt
                },
                triggered_by: utils.triggeredBy(origEvent),
                original_event: origEvent,
                continue: continueToken
            }]
        });
    }

    _sendResourceChanges(hyper, items, originalEvent, tag) {
        return hyper.post({
            uri: '/sys/queue/events',
            body: items.map((item) => {
                // TODO: need to check whether a wiki is http or https!
                const resourceURI =
                    `https://${item.domain}/wiki/${encodeURIComponent(item.title)}`;
                return {
                    meta: {
                        topic: 'resource_change',
                        schema_uri: 'resource_change/1',
                        uri: resourceURI,
                        request_id: originalEvent.meta.request_id,
                        domain: item.domain,
                        dt: originalEvent.meta.dt
                    },
                    triggered_by: utils.triggeredBy(originalEvent),
                    tags: [ 'change-prop', tag ]
                };
            })
        });
    }

    _getSiteInfo(hyper, message) {
        const domain = message.meta.domain;
        if (!this.siteInfoCache[domain]) {
            this.siteInfoCache[domain] = hyper.post(this.siteInfoRequest.expand({
                message: message
            }))
            .then((res) => {
                return {
                    general: {
                        lang: res.body.query.general.lang,
                        legaltitlechars: res.body.query.general.legaltitlechars,
                        case: res.body.query.general.case
                    },
                    namespaces: res.body.query.namespaces,
                    namespacealiases: res.body.query.namespacealiases
                };
            })
            .catch((e) => {
                hyper.log('error/site_info', e);
                delete this.siteInfoCache[domain];
                throw e;
            });
        }
        return this.siteInfoCache[domain];
    }
}

module.exports = (options) => {
    const processor = new DependencyProcessor(options);
    return {
        spec: {
            paths: {
                '/setup': {
                    put: {
                        summary: 'setup the module',
                        operationId: 'setup'
                    }
                },
                '/backlinks/{title}': {
                    post: {
                        summary: 'set up the kafka listener',
                        operationId: 'process_backlinks'
                    }
                },
                '/transcludes/{title}': {
                    post: {
                        summary: 'check if the page is transcluded somewhere and update',
                        operationId: 'process_transcludes'
                    }
                },
                '/wikidata_descriptions': {
                    post: {
                        summary: 'find all pages corresponding to ' +
                            'the wikidata item and update summary',
                        operationId: 'process_wikidata'
                    }
                }
            }
        },
        operations: {
            process_backlinks: processor.processBackLinks.bind(processor),
            process_transcludes: processor.processTranscludes.bind(processor),
            process_wikidata: processor.processWikidata.bind(processor),
            setup: processor.setup.bind(processor)
        },
        resources: [{
            uri: '/sys/links/setup'
        }]
    };
};
<commit_msg>Deduplicate continuation events (#98)<commit_after>"use strict";

const extend = require('extend');
const HyperSwitch = require('hyperswitch');
const Template = HyperSwitch.Template;
const utils = require('../lib/utils');
const Title = require('mediawiki-title').Title;

const BACKLINKS_CONTINUE_TOPIC_NAME = 'change-prop.backlinks.continue';
const TRANSCLUDES_CONTINUE_TOPIC_NAME = 'change-prop.transcludes.continue';
const DEDUPE_LOG_SIZE = 100;

function createBackLinksTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                list: 'backlinks',
                bltitle: '{{request.params.title}}',
                blfilterredir: 'nonredirects',
                blcontinue: '{{message.continue}}',
                bllimit: 500
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.blcontinue,
        continueTopic: BACKLINKS_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'backlinks',
        extractResults: (res) => res.body.query.backlinks
    };
}

function createImageUsageTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                list: 'imageusage',
                iutitle: '{{request.params.title}}',
                iucontinue: '{{message.continue}}',
                iulimit: 500
                // TODO: decide what do we want to do with redirects
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.iucontinue,
        continueTopic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'transcludes',
        extractResults: (res) =>  res.body.query.imageusage
    };
}

function createTranscludeInTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                prop: 'transcludedin',
                tiprop: 'title',
                tishow: '!redirect',
                titles: '{{request.params.title}}',
                ticontinue: '{{message.continue}}',
                tilimit: 500
            }
        })),
        getContinueToken: (res) => res.body.continue && res.body.continue.ticontinue,
        continueTopic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
        resourceChangeTag: 'transcludes',
        extractResults: (res) => {
            return res.body.query.pages[Object.keys(res.body.query.pages)[0]].transcludedin;
        }
    };
}

function createWikidataTemplate(options) {
    return {
        template: new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'wbgetentities',
                ids: '{{message.page_title}}',
                props: 'sitelinks/urls',
                normalize: true
            }
        })),
        resourceChangeTag: 'wikidata',
        shouldProcess: (res) => {
            if (!(res && res.body && !!res.body.success)) {
                return false;
            }
            const entities = res.body.entities || {};
            if (!Object.keys(entities).length) {
                return false;
            }
            const sitelinks = entities[Object.keys(entities)[0]].sitelinks || {};
            if (!Object.keys(sitelinks).length) {
                return false;
            }
            return true;
        },
        extractResults: (res) => {
            const siteLinks = res.body.entities[Object.keys(res.body.entities)[0]].sitelinks;
            return Object.keys(siteLinks).map((siteId) => {
                const pageURI = siteLinks[siteId].url;
                const match = /^https?:\/\/([^\/]+)\/wiki\/(.+)$/.exec(pageURI);
                return {
                    domain: match[1],
                    title: decodeURIComponent(match[2])
                };
            });
        }
    };
}

class DependencyProcessor {
    constructor(options) {
        this.options = options;
        this.log = options.log || function() { };
        this.siteInfoCache = {};
        this.backLinksRequest = createBackLinksTemplate(options);
        this.imageLinksRequest = createImageUsageTemplate(options);
        this.transcludeInRequest = createTranscludeInTemplate(options);
        this.wikidataRequest = createWikidataTemplate(options);
        this.siteInfoRequest = new Template(extend(true, {}, options.templates.mw_api, {
            method: 'post',
            body: {
                format: 'json',
                action: 'query',
                meta: 'siteinfo',
                siprop: 'general|namespaces|namespacealiases'
            }
        }));
        this.latestMessages = [];
    }

    setup(hyper) {
        return hyper.post({
            uri: '/sys/queue/subscriptions',
            body: {
                backlinks_continue: {
                    topic: BACKLINKS_CONTINUE_TOPIC_NAME,
                    exec: [
                        {
                            method: 'post',
                            uri: '/sys/links/backlinks/{message.original_event.page_title}',
                            body: '{{globals.message}}'
                        }
                    ]
                },
                transclusions_continue: {
                    topic: TRANSCLUDES_CONTINUE_TOPIC_NAME,
                    exec: [
                        {
                            method: 'post',
                            uri: '/sys/links/transcludes/{message.original_event.page_title}',
                            body: '{{globals.message}}'
                        }
                    ]
                }
            }
        });
    }

    processBackLinks(hyper, req) {
        const context = {
            request: req,
            message: req.body
        };
        const originalEvent = req.body.original_event || req.body;
        return this._getSiteInfo(hyper, req.body)
        .then((siteInfo) => {
            return this._fetchAndProcessBatch(hyper, this.backLinksRequest,
                context, siteInfo, originalEvent);
        });
    }

    _isDuplicate(message) {
        if (!message.continue) {
            // Not a continuation event, don't deduplicate
            return false;
        }

        const currEventInfo = {
            domain: message.meta.domain,
            title: message.original_event.page_title,
            id: message.original_event.meta.id,
            sequence_num: message.sequence_num
        };

        // Don't know the seq_num, probably old event, can't deduplicate
        if (currEventInfo.sequence_num === undefined) {
            return false;
        }

        if (this.latestMessages.some((oldEvent) => {
                return oldEvent.domain === currEventInfo.domain
                    && oldEvent.title === currEventInfo.title
                        // This represents forking - we've consumed some continuation twice,
                        // so we've got exact same event processed recently
                    && (oldEvent.id === currEventInfo.id && oldEvent.sequence_num === currEventInfo.sequence_num
                        // This represents deduplication on rapid consequent template changes
                        || oldEvent.id !== currEventInfo.id && oldEvent.sequence_num <= currEventInfo.sequence_num);
            })) {
            return true;
        }

        this.latestMessages.push(currEventInfo);
        if (this.latestMessages.length > DEDUPE_LOG_SIZE) {
            this.latestMessages = this.latestMessages.slice(1);
        }
        return false;
    }

    processTranscludes(hyper, req) {
        const message = req.body;
        const context = {
            request: req,
            message: message
        };
        const originalEvent = req.body.original_event || req.body;

        if (this._isDuplicate(message)) {
            hyper.metrics.increment('transclusions_continue_deduplicate');
            // Skip if duplication detected
            return { status: 200 };
        }
        return this._getSiteInfo(hyper, message)
        .then((siteInfo) => {
            const title = Title.newFromText(req.params.title, siteInfo);
            if (title.getNamespace().isFile()) {
                return this._fetchAndProcessBatch(hyper, this.imageLinksRequest,
                    context, siteInfo, originalEvent);
            }
            return this._fetchAndProcessBatch(hyper, this.transcludeInRequest,
                context, siteInfo, originalEvent);
        });
    }

    processWikidata(hyper, req) {
        const context = {
            request: req,
            message: req.body
        };
        return hyper.request(this.wikidataRequest.template.expand(context))
        .then((res) => {
            if (this.wikidataRequest.shouldProcess(res)) {
                const items = this.wikidataRequest.extractResults(res);
                return this._sendResourceChanges(hyper, items, req.body,
                    this.wikidataRequest.resourceChangeTag);
            } else if (res.body && res.body.error) {
                this.log('warn/wikidata_description', {
                    msg: 'Could not extract items',
                    event: context.message,
                    error: res.body.error
                });
            }
        })
        .thenReturn({ status: 200 });
    }

    _fetchAndProcessBatch(hyper, requestTemplate, context, siteInfo, originalEvent) {
        return hyper.post(requestTemplate.template.expand(context))
        .then((res) => {
            const titles = (requestTemplate.extractResults(res) || []).map((item) => {
                return {
                    title: Title.newFromText(item.title, siteInfo).getPrefixedDBKey(),
                    domain: originalEvent.meta.domain
                };
            });
            if (!titles.length) {
                // the batch is complete or the list of transcluded titles is empty
                return { status: 200 };
            }
            let actions = this._sendResourceChanges(hyper, titles,
                originalEvent, requestTemplate.resourceChangeTag);
            if (res.body.continue) {
                actions = actions.then(() =>
                    this._sendContinueEvent(hyper,
                        requestTemplate.continueTopic,
                        originalEvent,
                        requestTemplate.getContinueToken(res),
                        context.message.sequence_num));
            }
            return actions.thenReturn({ status: 200 });
        });
    }

    _sendContinueEvent(hyper, topic, origEvent, continueToken, sequenceNum) {
        return hyper.post({
            uri: '/sys/queue/events',
            body: [{
                meta: {
                    topic: topic,
                    schema_uri: 'continue/1',
                    uri: origEvent.meta.uri,
                    request_id: origEvent.meta.request_id,
                    domain: origEvent.meta.domain,
                    dt: origEvent.meta.dt
                },
                triggered_by: utils.triggeredBy(origEvent),
                original_event: origEvent,
                continue: continueToken,
                sequence_num: (sequenceNum || 0) + 1
            }]
        });
    }

    _sendResourceChanges(hyper, items, originalEvent, tag) {
        return hyper.post({
            uri: '/sys/queue/events',
            body: items.map((item) => {
                // TODO: need to check whether a wiki is http or https!
                const resourceURI =
                    `https://${item.domain}/wiki/${encodeURIComponent(item.title)}`;
                return {
                    meta: {
                        topic: 'resource_change',
                        schema_uri: 'resource_change/1',
                        uri: resourceURI,
                        request_id: originalEvent.meta.request_id,
                        domain: item.domain,
                        dt: originalEvent.meta.dt
                    },
                    triggered_by: utils.triggeredBy(originalEvent),
                    tags: [ 'change-prop', tag ]
                };
            })
        });
    }

    _getSiteInfo(hyper, message) {
        const domain = message.meta.domain;
        if (!this.siteInfoCache[domain]) {
            this.siteInfoCache[domain] = hyper.post(this.siteInfoRequest.expand({
                message: message
            }))
            .then((res) => {
                return {
                    general: {
                        lang: res.body.query.general.lang,
                        legaltitlechars: res.body.query.general.legaltitlechars,
                        case: res.body.query.general.case
                    },
                    namespaces: res.body.query.namespaces,
                    namespacealiases: res.body.query.namespacealiases
                };
            })
            .catch((e) => {
                hyper.log('error/site_info', e);
                delete this.siteInfoCache[domain];
                throw e;
            });
        }
        return this.siteInfoCache[domain];
    }
}

module.exports = (options) => {
    const processor = new DependencyProcessor(options);
    return {
        spec: {
            paths: {
                '/setup': {
                    put: {
                        summary: 'setup the module',
                        operationId: 'setup'
                    }
                },
                '/backlinks/{title}': {
                    post: {
                        summary: 'set up the kafka listener',
                        operationId: 'process_backlinks'
                    }
                },
                '/transcludes/{title}': {
                    post: {
                        summary: 'check if the page is transcluded somewhere and update',
                        operationId: 'process_transcludes'
                    }
                },
                '/wikidata_descriptions': {
                    post: {
                        summary: 'find all pages corresponding to ' +
                            'the wikidata item and update summary',
                        operationId: 'process_wikidata'
                    }
                }
            }
        },
        operations: {
            process_backlinks: processor.processBackLinks.bind(processor),
            process_transcludes: processor.processTranscludes.bind(processor),
            process_wikidata: processor.processWikidata.bind(processor),
            setup: processor.setup.bind(processor)
        },
        resources: [{
            uri: '/sys/links/setup'
        }]
    };
};
<|endoftext|>