From 79a61024a62b0299a7c6382a101557ac37cf9743 Mon Sep 17 00:00:00 2001 From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com> Date: Fri, 5 Aug 2022 16:05:48 +0200 Subject: [PATCH] feat: add konnector implementation --- manifest.konnector | 1 - src/contractActivation.js | 71 ++++++++++++++++++ src/contractTermination.js | 60 +++++++++++++++ src/contractVerification.js | 50 +++++++++++++ src/enum.js | 11 +++ src/index.js | 141 +++++++++++++++++++++++------------- src/parsing.js | 14 ++++ src/requests/bo.js | 47 +++++++++--- src/requests/cozy.js | 17 +++++ src/types.js | 52 ++++++++++--- src/verifyUserIdentity.js | 10 ++- 11 files changed, 399 insertions(+), 75 deletions(-) create mode 100644 src/contractActivation.js create mode 100644 src/contractTermination.js create mode 100644 src/contractVerification.js create mode 100644 src/enum.js create mode 100644 src/requests/cozy.js diff --git a/manifest.konnector b/manifest.konnector index e26d1d5..9915ed5 100644 --- a/manifest.konnector +++ b/manifest.konnector @@ -21,7 +21,6 @@ "permissions": { "accounts": { "type": "io.cozy.accounts", - "verbs": ["GET"] }, "files": { "type": "io.cozy.files" diff --git a/src/contractActivation.js b/src/contractActivation.js new file mode 100644 index 0000000..a1d02eb --- /dev/null +++ b/src/contractActivation.js @@ -0,0 +1,71 @@ +// @ts-check +const { log, errors } = require('cozy-konnector-libs') +const soapRequest = require('easy-soap-request') +const { parseTags, parseValue } = require('./parsing') +const { commanderCollectePublicationMesures } = require('./requests/sge') +const xml2js = require('xml2js') + +/** + * @param {string} url + * @param {string} apiAuthKey + * @param {string} appLogin + * @param {string} name + * @param {string} pointId + * @param {string} startDate + * @param {string} endDate + * @return {Promise<number>} User contractId + */ +async function activateContract( + url, + apiAuthKey, + appLogin, + contractId, + name, + pointId, + startDate, + endDate +) { + log('info', 'activateContract') + const sgeHeaders = { + 'Content-Type': 'text/xml;charset=UTF-8', + apikey: apiAuthKey, + } + + const { response } = await soapRequest({ + url: url, + headers: sgeHeaders, + xml: commanderCollectePublicationMesures( + appLogin, + contractId, + pointId, + name, + startDate, + endDate + ), + }).catch(err => { + log('error', 'rechercherPointResponse') + log('error', err) + throw errors.LOGIN_FAILED + }) + + const parsedReply = await xml2js.parseStringPromise(response.body, { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }) + + try { + // return parseUserPdl(parsedReply) + //TODO: Parse reply + console.log( + '🚀 ~ file: contractActivation.js ~ line 56 ~ parsedReply', + parsedReply + ) + return 1 + } catch (error) { + log('error', 'Error while parsing user PDL: ' + error) + throw errors.LOGIN_FAILED + } +} + +module.exports = { activateContract } diff --git a/src/contractTermination.js b/src/contractTermination.js new file mode 100644 index 0000000..46a8f2f --- /dev/null +++ b/src/contractTermination.js @@ -0,0 +1,60 @@ +// @ts-check +const { log, errors } = require('cozy-konnector-libs') +const soapRequest = require('easy-soap-request') +const { parseTags, parseValue } = require('./parsing') +const { commanderArretServiceSouscritMesures } = require('./requests/sge') +const xml2js = require('xml2js') + +/** + * @param {string} url + * @param {string} apiAuthKey + * @param {string} appLogin + * @param {string} pointId + * @param {number} serviceId + * @return {Promise<string>} User contractId + */ +async function terminateContract( + url, + apiAuthKey, + appLogin, + contractId, + pointId, + serviceId +) { + log('info', 'activateContract') + const sgeHeaders = { + 'Content-Type': 'text/xml;charset=UTF-8', + apikey: apiAuthKey, + } + + const { response } = await soapRequest({ + url: url, + headers: sgeHeaders, + xml: commanderArretServiceSouscritMesures( + appLogin, + contractId, + pointId, + serviceId + ), + }).catch(err => { + log('error', 'commanderArretServiceSouscritMesures') + log('error', err) + throw errors.VENDOR_DOWN + }) + + const parsedReply = await xml2js.parseStringPromise(response.body, { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }) + + try { + // We don't need any action on reply for now + return parsedReply + } catch (error) { + log('error', 'Error while parsing user contract termination: ' + error) + throw errors.VENDOR_DOWN + } +} + +module.exports = { terminateContract } diff --git a/src/contractVerification.js b/src/contractVerification.js new file mode 100644 index 0000000..327aaab --- /dev/null +++ b/src/contractVerification.js @@ -0,0 +1,50 @@ +// @ts-check +const { log, errors } = require('cozy-konnector-libs') +const soapRequest = require('easy-soap-request') +const { parseTags, parseValue, parseContracts } = require('./parsing') +const { rechercherServicesSouscritsMesures } = require('./requests/sge') +const xml2js = require('xml2js') +const { contractState } = require('./enum') + +/** + * @param {string} url + * @param {string} apiAuthKey + * @param {string} appLogin + * @param {string} pointId + * @return {Promise<number | null>} User contractId + */ +async function verifyContract(url, apiAuthKey, appLogin, contractId, pointId) { + log('info', 'verifyContract') + const sgeHeaders = { + 'Content-Type': 'text/xml;charset=UTF-8', + apikey: apiAuthKey, + } + + const { response } = await soapRequest({ + url: `${url}/enedis_SGE_RechercheServicesMesures/1.0`, + headers: sgeHeaders, + xml: rechercherServicesSouscritsMesures(appLogin, contractId, pointId), + }).catch(err => { + log('error', 'rechercherServicesSouscritsMesures') + log('error', err) + throw errors.LOGIN_FAILED + }) + + const parsedReply = await xml2js.parseStringPromise(response.body, { + tagNameProcessors: [parseTags], + valueProcessors: [parseValue], + explicitArray: false, + }) + + try { + const currentContract = parseContracts(parsedReply)[0] + if (currentContract.etatCode === contractState.ACTIF) + return currentContract.serviceSouscritId + return null + } catch (error) { + log('error', 'Error while parsing user contract: ' + error) + throw errors.LOGIN_FAILED + } +} + +module.exports = { verifyContract } diff --git a/src/enum.js b/src/enum.js new file mode 100644 index 0000000..9e744e3 --- /dev/null +++ b/src/enum.js @@ -0,0 +1,11 @@ +/** + * Enum for contract-state values. + * @readonly + * @enum {number} + */ +const contractState = { + TERMINE: 'TERMINE', + ACTIF: 'ACTIF', +} + +module.exports = { contractState } diff --git a/src/index.js b/src/index.js index 69887a4..3b471b4 100644 --- a/src/index.js +++ b/src/index.js @@ -22,17 +22,18 @@ const { consulterDonneesTechniquesContractuelles, consultationMesuresDetailleesMaxPower, consultationMesuresDetaillees, - - commanderCollectePublicationMesures, - commanderArretServiceSouscritMesures, } = require('./requests/sge') const { updateBoConsent, - // createBoConsent, + createBoConsent, getBoConsent, deleteBoConsent, } = require('./requests/bo') const { verifyUserIdentity } = require('./verifyUserIdentity') +const { activateContract } = require('./contractActivation') +const { verifyContract } = require('./contractVerification') +const { terminateContract } = require('./contractTermination') +const { saveAccountData } = require('./requests/cozy') moment.locale('fr') // set the language moment.tz.setDefault('Europe/Paris') // set the timezone @@ -51,18 +52,22 @@ const endDateString = endDate.format('YYYY-MM-DD') module.exports = new BaseKonnector(start) module.exports = { getContractStartDate } -// 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 -// cozyParameters are static parameters, independents from the account. Most often, it can be a -// secret api key. +/** + * 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 + * cozyParameters are static parameters, independents from the account. Most often, it can be a + * secret api key. + * @param {fields} fields + * @param {{secret: fields}} cozyParameters + */ async function start(fields, cozyParameters) { log('info', 'Gathering data ...') let pointId = fields.pointId let baseUrl = fields.wso2BaseUrl let apiAuthKey = fields.apiToken //TODO switch variable to english - let loginUtilisateur = fields.loginUtilisateur + let sgeLogin = fields.sgeLogin log('info', 'Authenticating ...') //TODO: Verify if condition is working in local and on build version if (cozyParameters && Object.keys(cozyParameters).length !== 0) { @@ -70,7 +75,7 @@ async function start(fields, cozyParameters) { pointId = cozyParameters.secret.pointId baseUrl = cozyParameters.secret.wso2BaseUrl apiAuthKey = cozyParameters.secret.apiToken - loginUtilisateur = cozyParameters.secret.loginUtilisateur + sgeLogin = cozyParameters.secret.sgeLogin } /** @@ -83,78 +88,109 @@ async function start(fields, cozyParameters) { */ log('info', 'User Logging...') - if (await isFirstStart()) { - await verifyUserIdentity(fields, baseUrl, apiAuthKey, loginUtilisateur) - - // await createBoConsent() - //TODO: remove because useless ? Done later in code - // const startDate = await getDataStartDate( - // baseUrl, - // apiAuthKey, - // loginUtilisateur, - // fields.pointId - // ) - // console.log(startDate) - await getContractStartDate(baseUrl, apiAuthKey, loginUtilisateur, pointId) - - console.log(startDailyDate) - console.log(startDailyDateString) - - throw new Error('Rest not implemented.') - // await commanderCollectePublicationMesures() - // await updateBoConsent() + if (await isFirstStart(this.getAccountData())) { + const user = await verifyUserIdentity(fields, baseUrl, apiAuthKey, sgeLogin) + + let consent = await createBoConsent( + pointId, + user.name, + user.address, + user.postalCode, + user.inseeCode + ) + await getContractStartDate(baseUrl, apiAuthKey, sgeLogin, pointId) + + const contractStartDate = moment().format('YYYY-MM-DD') + //TODO: consent time ? 5 years? + const contractEndDate = moment() + .add(1, 'year') + .format('YYYY-MM-DD') + + let serviceId = await verifyContract( + baseUrl, + apiAuthKey, + sgeLogin, + fields.contractId, + user.pointId + ) + if (!serviceId) { + serviceId = await activateContract( + baseUrl, + apiAuthKey, + sgeLogin, + fields.contractId, + user.name, + user.pointId, + contractStartDate, + contractEndDate + ) + } + consent = await updateBoConsent(consent, serviceId) + // Save bo id into account + const accountData = await this.getAccountData() + await saveAccountData(this.accountId, { ...accountData, boId: consent.id }) } else { - //AlternateStart - await getBoConsent() - if (!(await verifyUserIdentity(fields))) { + // AlternateStart + //TODO: getboid from account ? + const accountData = await this.getAccountData() + const userConsent = await getBoConsent(accountData.data.boId) + const user = await verifyUserIdentity(fields, baseUrl, apiAuthKey, sgeLogin) + if (user.name !== userConsent.name || !user) { await deleteBoConsent() - await commanderArretServiceSouscritMesures() + if (userConsent.serviceId) { + await terminateContract( + baseUrl, + apiAuthKey, + sgeLogin, + fields.contractId, + fields.pointId, + userConsent.serviceId + ) + } else { + log('error', `No service id retrieved from BO`) + throw errors.VENDOR_DOWN + } throw errors.TERMS_VERSION_MISMATCH } } log('info', 'Successfully logged in') - await gatherData(baseUrl, apiAuthKey, loginUtilisateur, fields.pointId) + await gatherData(baseUrl, apiAuthKey, sgeLogin, fields.pointId) } /** * Main method for gathering data * @param {string} baseUrl * @param {string} apiAuthKey - * @param {string} loginUtilisateur + * @param {string} sgeLogin * @param {number} pointId */ -async function gatherData(baseUrl, apiAuthKey, loginUtilisateur, pointId) { +async function gatherData(baseUrl, apiAuthKey, sgeLogin, pointId) { log('info', 'Querying data...') - await getContractStartDate( - `${baseUrl}/enedis_SGE_ConsultationDonneesTechniquesContractuelles/1.0`, - apiAuthKey, - loginUtilisateur, - pointId - ) + await getContractStartDate(baseUrl, apiAuthKey, sgeLogin, pointId) await getData( `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`, apiAuthKey, - loginUtilisateur, + sgeLogin, pointId ) await getMaxPowerData( `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`, apiAuthKey, - loginUtilisateur, + sgeLogin, pointId ) await getDataHalfHour( `${baseUrl}/enedis_SGE_ConsultationMesuresDetaillees/1.0`, apiAuthKey, - loginUtilisateur, + sgeLogin, pointId ) log('info', 'Querying data: done') } /** - * + * //TODO: Move * @param {string} url * @param {string} apiAuthKey * @param {string} userLogin @@ -445,8 +481,11 @@ async function agregateMonthAndYearData(data) { /** * @returns {boolean} */ -function isFirstStart() { - console.log('isFirstStart') - //TODO: Implement +async function isFirstStart(account) { + if (account.data && account.data.boId) { + log('info', 'Konnector not first start') + return false + } + log('info', 'Konnector first start') return true } diff --git a/src/parsing.js b/src/parsing.js index b4be6d8..ebda5c2 100644 --- a/src/parsing.js +++ b/src/parsing.js @@ -30,6 +30,19 @@ function parseContractStartDate(result) { ] } +/** + * Return User contract start date + * @param {string} result + * @returns {Contract[]} + */ +function parseContracts(result) { + log('info', 'Parsing contract') + const json = JSON.stringify(result) + return JSON.parse(json)['Envelope']['Body'][ + 'rechercherServicesSouscritsMesuresResponse' + ]['servicesSouscritsMesures']['serviceSouscritMesures'] +} + /** * Parsing SGE xml reply to get only mesure data * @param {string} result @@ -95,5 +108,6 @@ module.exports = { parseTags, parseValue, parseUserPdl, + parseContracts, parseContractStartDate, } diff --git a/src/requests/bo.js b/src/requests/bo.js index 53323c1..55c0210 100644 --- a/src/requests/bo.js +++ b/src/requests/bo.js @@ -1,38 +1,63 @@ // @ts-check -const { log } = require('cozy-konnector-libs') +const { log, errors } = require('cozy-konnector-libs') /** - * + * @param {number} pointId + * @param {string} name + * @param {string} address + * @param {string} postalCode + * @param {string} inseeCode + * @returns {Consent} */ -function createBoConsent() { +function createBoConsent(pointId, name, address, postalCode, inseeCode) { //TODO: Implement log('info', `Query createBoConsent`) - throw new Error('Function not implemented.') + return { + pointId, + name, + address, + postalCode, + inseeCode, + } } /** - * + * @param {Consent} consent + * @param {number} serviceId + * @returns {Consent} */ -function updateBoConsent() { +function updateBoConsent(consent, serviceId) { //TODO: Implement log('info', `Query updateBoConsent`) - throw new Error('Function not implemented.') + return { + ...consent, + serviceId: serviceId, + } } /** - * + * @param {number} boId + * @returns {Consent} */ -function getBoConsent() { +function getBoConsent(boId) { //TODO: Implement log('info', `Query getBoConsent`) - throw new Error('Function not implemented.') + return { + pointId: 1234, + name: 'SUBTIL', + address: 'mad', + postalCode: '69007', + inseeCode: '69383', + serviceId: 1234, + } + // throw errors.VENDOR_DOWN } /** * */ function deleteBoConsent() { //TODO: deleteBoConsent - log('info', `Query createBoConsent`) + log('info', `Query deleteBoConsent`) throw new Error('Function not implemented.') } diff --git a/src/requests/cozy.js b/src/requests/cozy.js new file mode 100644 index 0000000..a02ebcb --- /dev/null +++ b/src/requests/cozy.js @@ -0,0 +1,17 @@ +const { log, updateOrCreate } = require('cozy-konnector-libs') +const cozyClient = require('cozy-konnector-libs/dist/libs/cozyclient') + +async function saveAccountData(accountId, accountData) { + log('info', `saveAccountData: ${accountId}`) + const accounts = await cozyClient.data.findAll('io.cozy.accounts') + + //TODO: refactor with usageof cozy-libs. Not working during implementation + const account = accounts.filter(account => account._id === accountId) + + return updateOrCreate( + [{ ...account[0], data: accountData }], + 'io.cozy.accounts' + ) +} + +module.exports = { saveAccountData } diff --git a/src/types.js b/src/types.js index b5cff38..99452fd 100644 --- a/src/types.js +++ b/src/types.js @@ -15,24 +15,54 @@ * @property {string} d */ -// /** -// * User definition -// * @typedef {object} User -// * @property {string} name -// * @property {string} address -// * @property {string} postalCode -// * @property {string} pointId -// * @property {string} [inseeCode] -// */ +/** + * Fields definition + * @typedef {object} fields + * @property {string} wso2BaseUrl + * @property {string} apiToken + * @property {string} sgeLogin + * @property {string} contractId + * @property {unumber} pointId + */ /** * Consent definition * @typedef {object} Consent * @property {number} pointId * @property {string} name - * @property {string} adresse + * @property {string} address * @property {string} postalCode * @property {string} inseeCode - * @property {string} [serviceId] + * @property {number} [serviceId] * @property {number} [id] */ + +/** + * User definition + * @typedef {object} User + * @property {string} pointId + * @property {string} name + * @property {string} postalCode + * @property {string} address + * @property {string} inseeCode + */ + +/** + * Contract definition + * @typedef {object} Contract + * @property {number} serviceSouscritId + * @property {string} pointId + * @property {object} serviceSouscritType + * @property {string} serviceSouscritLibelle + * @property {string} injection + * @property {string} soutirage + * @property {string} contratId + * @property {string} contratLibelle + * @property {contractState} etatCode + * @property {string} dateDebut + * @property {string} dateFin + * @property {string} mesuresTypeCode + * @property {string} mesuresPas + * @property {string} mesuresCorrigees + * @property {string} periodiciteTransmission + */ diff --git a/src/verifyUserIdentity.js b/src/verifyUserIdentity.js index 2d46ced..9db4076 100644 --- a/src/verifyUserIdentity.js +++ b/src/verifyUserIdentity.js @@ -9,7 +9,7 @@ const { getInseeCode } = require('./requests/insee') * @param {string} baseUrl * @param {string} apiAuthKey * @param {string} loginUtilisateur - * @returns {Promise<void>} + * @returns {Promise<User>} */ async function verifyUserIdentity( fields, @@ -33,6 +33,14 @@ async function verifyUserIdentity( log('error', 'PointId does not match') throw errors.LOGIN_FAILED } + + return { + name: fields.name, + pointId: fields.pointId, + inseeCode, + postalCode: fields.postalCode, + address: fields.address, + } } module.exports = { verifyUserIdentity } -- GitLab