<commit_before>/* global fetch */
/* global cozy */
import { Component } from 'react'

import * as accounts from './accounts'
import * as konnectors from './konnectors'

const INSTALL_TIMEOUT = 120 * 1000

export default class CollectStore {
  constructor (connectors, folders, context) {
    this.listener = null
    this.connectors = connectors
    this.folders = folders
    this.useCases = require(`../contexts/${context}/index`).useCases
  }

  subscribeTo (connectorId, listener) {
    this.listener = listener
    return this.find(c => c.id === connectorId)
  }

  sanitizeKonnector (konnector) {
    const sanitized = Object.assign({}, konnector)

    // disallow empty category
    if (sanitized.category === '') {
      delete sanitized.category
    }

    // disallow updating name and slug
    delete sanitized.name
    delete sanitized.slug
    // custom message only from collect json config
    delete sanitized.additionnalSuccessMessage
    // hasDescriptions only from collect json config
    delete sanitized.hasDescriptions
    // fields settings only from collect json config
    delete sanitized.fields

    return sanitized
  }

  // Merge source into target
  mergeKonnectors (source, target) {
    return Object.assign({}, target, this.sanitizeKonnector(source))
  }

  updateConnector (connector) {
    if (!connector) throw new Error('Missing mandatory connector parameter')
    const slug = connector.slug || connector.attributes.slug
    if (!slug) throw new Error('Missing mandatory slug property in konnector')

    const current = this.connectors.find(connector => connector.slug === slug)
    const updatedConnector = this.mergeKonnectors(connector, current)

    this.connectors = this.connectors.map(connector => {
      return connector === current
        ? updatedConnector
          : connector
    })

    return updatedConnector
  }

  getCategories () {
    return this.connectors.map(a => a.category).filter((cat, idx, all) => all.indexOf(cat) === idx)
  }

  getUseCases () {
    return this.useCases
  }

  find (cb) {
    return this.connectors.find(cb)
  }

  findConnected () {
    return this.connectors.filter(c => c.accounts.length !== 0)
  }

  findByCategory ({filter}) {
    return filter === 'all' ? this.connectors
      : this.connectors.filter(c => c.category === filter)
  }

  findByUseCase (slug) {
    let useCase = this.useCases.find(u => u.slug === slug)
    return useCase.connectors.map(c1 => this.find(c2 => c1.slug === c2.slug))
  }

  // Fetch all accounts and updates their matching connectors
  fetchAllAccounts () {
    return accounts.getAllAccounts(cozy.client, this.accountsIndex)
      .then(accounts => {
        return Promise.all([accounts, konnectors.getAllErrors(cozy.client)])
      })
      .then((result) => {
        const [accounts, errors] = result
        const errorIndex = errors.reduce((memo, error) => {
          memo[error.account] = error
          return memo
        }, {})

        let accObject = {}
        accounts.forEach(a => {
          if (!accObject[a.account_type]) accObject[a.account_type] = []
          if (errorIndex[a._id] && errorIndex[a._id].error) {
            a.error = errorIndex[a._id].error
            accObject[a.account_type].error = errorIndex[a._id].error
          }
          accObject[a.account_type].push(a)
        })
        this.connectors.forEach(c => {
          c.accounts = accObject[c.slug] || []
        })
      })
  }

  // Account connection workflow, see
  // https://github.com/cozy/cozy-stack/blob/master/docs/konnectors_workflow_example.md
  connectAccount (konnector, account, folderPath) {
    // return object to store all business object implied in the connection
    const connection = {}
    // detect oauth case
    const isOAuth = !!account.oauth

    // 1. Create folder, will be replaced by an intent or something else
    return cozy.client.files.createDirectoryByPath(folderPath)
      // 2. Create account
      .then(folder => {
        connection.folder = folder
        if (isOAuth) {
          const newAttributes = {
            folderId: folder._id,
            status: 'PENDING'
          }
          return accounts.update(cozy.client, account, Object.assign({}, account, newAttributes))
        } else {
          return accounts.create(cozy.client, konnector, account.auth, folder)
        }
      })
      // 3. Konnector installation
      .then(account => {
        connection.account = account
        return konnectors.install(cozy.client, konnector, INSTALL_TIMEOUT)
      })
      // 4. Add account to konnector
      .then(konnector => {
        return konnectors.addAccount(cozy.client, konnector, connection.account)
      })
      // 5. Add permissions to folder for konnector
      .then(konnector => {
        connection.konnector = konnector
        this.updateConnector(konnector)
        return konnectors.addFolderPermission(cozy.client, konnector, connection.folder._id)
      })
      // 6. Reference konnector in folder
      .then(permission => {
        connection.permission = permission
        return cozy.client.data.addReferencedFiles(connection.konnector, connection.folder._id)
      })
      // 7. Run a job for the konnector
      .then(() => konnectors.run(
        cozy.client,
        connection.konnector,
        connection.account))
      // 8. Creates trigger
      .then(job => {
        connection.job = job
        const slug = connection.konnector.slug || connection.konnector.attributes.slug
        return cozy.client.fetchJSON('POST', '/jobs/triggers', {
          data: {
            attributes: {
              type: '@cron',
              arguments: '0 0 0 * * *',
              worker: 'konnector',
              worker_arguments: {
                konnector: slug,
                account: connection.account._id,
                folderToSave: connection.folder._id
              }<commit_msg>feat: now run the trigger every week<commit_after>/* global fetch */
/* global cozy */
import { Component } from 'react'

import * as accounts from './accounts'
import * as konnectors from './konnectors'

const INSTALL_TIMEOUT = 120 * 1000

export default class CollectStore {
  constructor (connectors, folders, context) {
    this.listener = null
    this.connectors = connectors
    this.folders = folders
    this.useCases = require(`../contexts/${context}/index`).useCases
  }

  subscribeTo (connectorId, listener) {
    this.listener = listener
    return this.find(c => c.id === connectorId)
  }

  sanitizeKonnector (konnector) {
    const sanitized = Object.assign({}, konnector)

    // disallow empty category
    if (sanitized.category === '') {
      delete sanitized.category
    }

    // disallow updating name and slug
    delete sanitized.name
    delete sanitized.slug
    // custom message only from collect json config
    delete sanitized.additionnalSuccessMessage
    // hasDescriptions only from collect json config
    delete sanitized.hasDescriptions
    // fields settings only from collect json config
    delete sanitized.fields

    return sanitized
  }

  // Merge source into target
  mergeKonnectors (source, target) {
    return Object.assign({}, target, this.sanitizeKonnector(source))
  }

  updateConnector (connector) {
    if (!connector) throw new Error('Missing mandatory connector parameter')
    const slug = connector.slug || connector.attributes.slug
    if (!slug) throw new Error('Missing mandatory slug property in konnector')

    const current = this.connectors.find(connector => connector.slug === slug)
    const updatedConnector = this.mergeKonnectors(connector, current)

    this.connectors = this.connectors.map(connector => {
      return connector === current
        ? updatedConnector
          : connector
    })

    return updatedConnector
  }

  getCategories () {
    return this.connectors.map(a => a.category).filter((cat, idx, all) => all.indexOf(cat) === idx)
  }

  getUseCases () {
    return this.useCases
  }

  find (cb) {
    return this.connectors.find(cb)
  }

  findConnected () {
    return this.connectors.filter(c => c.accounts.length !== 0)
  }

  findByCategory ({filter}) {
    return filter === 'all' ? this.connectors
      : this.connectors.filter(c => c.category === filter)
  }

  findByUseCase (slug) {
    let useCase = this.useCases.find(u => u.slug === slug)
    return useCase.connectors.map(c1 => this.find(c2 => c1.slug === c2.slug))
  }

  // Fetch all accounts and updates their matching connectors
  fetchAllAccounts () {
    return accounts.getAllAccounts(cozy.client, this.accountsIndex)
      .then(accounts => {
        return Promise.all([accounts, konnectors.getAllErrors(cozy.client)])
      })
      .then((result) => {
        const [accounts, errors] = result
        const errorIndex = errors.reduce((memo, error) => {
          memo[error.account] = error
          return memo
        }, {})

        let accObject = {}
        accounts.forEach(a => {
          if (!accObject[a.account_type]) accObject[a.account_type] = []
          if (errorIndex[a._id] && errorIndex[a._id].error) {
            a.error = errorIndex[a._id].error
            accObject[a.account_type].error = errorIndex[a._id].error
          }
          accObject[a.account_type].push(a)
        })
        this.connectors.forEach(c => {
          c.accounts = accObject[c.slug] || []
        })
      })
  }

  // Account connection workflow, see
  // https://github.com/cozy/cozy-stack/blob/master/docs/konnectors_workflow_example.md
  connectAccount (konnector, account, folderPath) {
    // return object to store all business object implied in the connection
    const connection = {}
    // detect oauth case
    const isOAuth = !!account.oauth

    // 1. Create folder, will be replaced by an intent or something else
    return cozy.client.files.createDirectoryByPath(folderPath)
      // 2. Create account
      .then(folder => {
        connection.folder = folder
        if (isOAuth) {
          const newAttributes = {
            folderId: folder._id,
            status: 'PENDING'
          }
          return accounts.update(cozy.client, account, Object.assign({}, account, newAttributes))
        } else {
          return accounts.create(cozy.client, konnector, account.auth, folder)
        }
      })
      // 3. Konnector installation
      .then(account => {
        connection.account = account
        return konnectors.install(cozy.client, konnector, INSTALL_TIMEOUT)
      })
      // 4. Add account to konnector
      .then(konnector => {
        return konnectors.addAccount(cozy.client, konnector, connection.account)
      })
      // 5. Add permissions to folder for konnector
      .then(konnector => {
        connection.konnector = konnector
        this.updateConnector(konnector)
        return konnectors.addFolderPermission(cozy.client, konnector, connection.folder._id)
      })
      // 6. Reference konnector in folder
      .then(permission => {
        connection.permission = permission
        return cozy.client.data.addReferencedFiles(connection.konnector, connection.folder._id)
      })
      // 7. Run a job for the konnector
      .then(() => konnectors.run(
        cozy.client,
        connection.konnector,
        connection.account))
      // 8. Creates trigger
      .then(job => {
        connection.job = job
        const slug = connection.konnector.slug || connection.konnector.attributes.slug
        return cozy.client.fetchJSON('POST', '/jobs/triggers', {
          data: {
            attributes: {
              type: '@cron',
              arguments: `0 0 0 * * ${(new Date()).getDay()}`,
              worker: 'konnector',
              worker_arguments: {
                konnector: slug,
                account: connection.account._id,
                folderToSave: connection.folder._id
              }
{'fraction_non_alphanumeric': 0.11391978429389957, 'fraction_numerical': 0.003875968992248062, 'mean_word_length': 2.4533022985161477, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 47, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3065406', 'n_tokens_mistral': 3489, 'n_tokens_neox': 3386, 'n_words': 796}