Skip to content
Snippets Groups Projects
index.js 7.39 KiB
Newer Older
Romain CREY's avatar
Romain CREY committed
const {
  BaseKonnector,
  log,
  errors,
  addData,
  hydrateAndFilter,
  cozyClient
} = require('cozy-konnector-libs')
const rp = require('request-promise')
const moment = require('moment')
require('moment-timezone')

moment.locale('fr') // set the language
moment.tz.setDefault('Europe/Paris') // set the timezone

const startDate = moment()
  .startOf('year')
  .subtract(3, 'year')
  .subtract(1, 'day')
Romain CREY's avatar
Romain CREY committed
  .format('MM/DD/YYYY')
const endDate = moment().format('MM/DD/YYYY')
const timeRange = ['day', 'month', 'year']
const rangeDate = {
  day: {
    doctype: 'com.grandlyon.egl.day',
    keys: ['year', 'month', 'day']
  },
  month: {
    doctype: 'com.grandlyon.egl.month',
    keys: ['year', 'month']
  },
  year: {
    doctype: 'com.grandlyon.egl.year',
Romain CREY's avatar
Romain CREY committed

module.exports = new BaseKonnector(start)

// The start function is run by the BaseKonnector instance only when it got all the account
// information (fields). When you run this connector yourself in "standalone" mode or "dev" mode,
// the account information come from ./konnector-dev-config.json file
async function start(fields, cozyParameters) {
  try {
    // resetting data for demo only
    // await resetData()
    const baseUrl = cozyParameters.secret.eglBaseURL
    const apiAuthKey = cozyParameters.secret.eglAPIAuthKey
    log('debug', fields, 'Fields')
Romain CREY's avatar
Romain CREY committed

    log('info', 'Authenticating ...')
    const response = await authenticate(
      fields.login,
      fields.password,
      baseUrl,
      apiAuthKey
    )
    log('info', 'Successfully logged in')
    Promise.all(
      timeRange.map(timeStep =>
        processData(timeStep, response, baseUrl, apiAuthKey)
      )
    )
Romain CREY's avatar
Romain CREY committed
  } catch (error) {
    throw new Error(error.message)
  }
}
async function processData(timeStep, response, baseUrl, apiAuthKey) {
  const doctype = rangeDate[timeStep].doctype
  log('info', 'Getting data')
  const loadProfile = await getData(response, baseUrl, apiAuthKey)
  log('info', 'Saving data to Cozy')
  if (doctype === rangeDate.day.doctype) {
    log('info', 'Saving daily data')
    await storeData(loadProfile, rangeDate.day.doctype, rangeDate.day.keys)
  } else if (doctype === rangeDate.month.doctype) {
    log('info', 'Saving monthly data')
    await resetInProgressAggregatedData(rangeDate.month.doctype)
    const monthlyData = processMonthlyAggregation(loadProfile, rangeDate.month)
    await storeData(monthlyData, rangeDate.month.doctype, rangeDate.month.keys)
  } else if (doctype === rangeDate.year.doctype) {
    log('info', 'Saving yearly data')
    await resetInProgressAggregatedData(rangeDate.year.doctype)
    const yearlyData = processYearAggregation(
      loadProfile,
      rangeDate.year.doctype
    )
    await storeData(yearlyData, rangeDate.year.doctype, rangeDate.year.keys)
  } else {
    throw new Error('Unkonw range type: ' + doctype)
  }
}
Romain CREY's avatar
Romain CREY committed

async function authenticate(login, password, baseUrl, apiAuthKey) {
  const authRequest = {
    method: 'POST',
    uri: baseUrl + '/connect.aspx',
    headers: {
      AuthKey: apiAuthKey,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    form: {
      login: login,
      pass: password
    },
    json: true
  }
Yoan VALLET's avatar
Yoan VALLET committed
  const response = await rp(authRequest)
  if (response.codeRetour === 100) {
    return response
  } else {
    throw new Error(errors.LOGIN_FAILED)
Romain CREY's avatar
Romain CREY committed
  }
}

async function getData(response, baseUrl, apiAuthKey) {
  log('debug', startDate, 'Start date')
  log('debug', endDate, 'End date')
Romain CREY's avatar
Romain CREY committed
  const dataRequest = {
    method: 'POST',
    uri: baseUrl + '/getAllAgregatsByAbonnement.aspx',
    headers: {
      AuthKey: apiAuthKey,
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    form: {
      token: response.resultatRetour.token,
      num_abt: response.resultatRetour.num_abt,
      date_debut: startDate,
      date_fin: endDate
    },
    json: true
  }
  try {
Yoan VALLET's avatar
Yoan VALLET committed
    const responseEgl = await rp(dataRequest)
    switch (responseEgl.codeRetour) {
Romain CREY's avatar
Romain CREY committed
      case 100:
Yoan VALLET's avatar
Yoan VALLET committed
        return format(responseEgl)
Romain CREY's avatar
Romain CREY committed
      case -2:
        throw new Error(errors.LOGIN_FAILED)
      case -1:
        throw new Error(errors.VENDOR_DOWN)
      default:
        throw new Error(errors.UNKNOWN_ERROR)
    }
  } catch (error) {
    throw new Error(errors.VENDOR_DOWN)
  }
}

function format(response) {
  const data = response.resultatRetour.slice(1).map((value, index) => {
    const time = moment(value.DateReleve, moment.ISO_8601)
Romain CREY's avatar
Romain CREY committed
    return {
      load: value.ValeurIndex - response.resultatRetour[index].ValeurIndex,
      year: parseInt(time.format('YYYY')),
      month: parseInt(time.format('M')),
      day: parseInt(time.format('D')),
      hour: 0,
      minute: 0,
Romain CREY's avatar
Romain CREY committed
      type: value.TypeAgregat
    }
  })
  return data
}

function processYearAggregation(data, doctype) {
  log('info', 'Start aggregation for : ' + doctype)
  const grouped = data.reduce(reduceYearFunction, {})
  return Object.values(grouped)
}

function processMonthlyAggregation(data, range) {
  log('info', 'Start aggregation for : ' + range.doctype)
  // Filter by year
  const tmpData = groupBy(data, 'year')
  const keys = Object.keys(tmpData)
  var dataToStore = []
  // Monthly aggregation
  for (const index in keys) {
    // Get daily data of a year
    var monthlyData = tmpData[keys[index]]
    // Monthly aggregation
    var aggregatedData = monthlyData.reduce(reduceMonthFunction, {})
    // Store it
    dataToStore = dataToStore.concat(Object.values(aggregatedData))
  }
  return dataToStore
}

function groupBy(xs, key) {
  return xs.reduce(function(rv, x) {
    ;(rv[x[key]] = rv[x[key]] || []).push(x)
    return rv
  }, {})
}

function reduceYearFunction(acc, x) {
  var id = acc[x.year]
  if (id) {
    id.load += x.load
  } else {
    acc[x.year] = x
  }
  return acc
}

function reduceMonthFunction(acc, x) {
  var id = acc[x.month]
  if (id) {
    id.load += x.load
  } else {
    acc[x.month] = x
  }
  return acc
}

async function storeData(data, doctype, keys) {
  log('debug', 'Store into ' + doctype)
  log('debug', keys, 'Store into keys ')
  return hydrateAndFilter(data, doctype, {
    keys: keys
Romain CREY's avatar
Romain CREY committed
  }).then(filteredDocuments => {
    addData(filteredDocuments, doctype)
/**
 * Function handling special case.
 * The temporary aggregated data need to be remove in order for the most recent one te be saved.
 * ex for com.grandlyon.egl.month :
 * { load: 76.712, month: 2020, ... } need to be replace by
 * { load: 82.212, month: 2020, ... } after grdf data reprocess
 */
async function resetInProgressAggregatedData(doctype) {
  // /!\ Warning: cannot use mongo queries because not supported for dev by cozy-konnectors-libs
  log('debug', doctype, 'Remove aggregated data for')
  const result = await cozyClient.data.findAll(doctype)
  if (result.error || result.length <= 0) {
Romain CREY's avatar
Romain CREY committed
    // eslint-disable-next-line no-console
    console.warn('Error while fetching loads, doctype not found ')
  } else {
    const currentDate = moment()
    // Filter data to remove
    var filtered = []
    if (doctype === rangeDate.year.doctype) {
      // Yearly case
      filtered = result.filter(function(el) {
        return el.year == currentDate.year()
      })
    } else {
      // Monthly case
      filtered = result.filter(function(el) {
        return (
          el.year == currentDate.year() &&
          el.month == parseInt(moment().format('M'))
        )
      })
    }
    // Remove data
    for (const doc of filtered) {
      log('debug', doc, 'Removing this entry for ' + doctype)
      await cozyClient.data.delete(doctype, doc)
    }
Romain CREY's avatar
Romain CREY committed
  }
}