<commit_before>'use strict';

const unirest = require('unirest');
const validate = require('validate.js');
const _ = require('lodash');

const Common = require('./common');

function SMS(options) {

    const _self = this;

    this.options = options;

    this._send = function (params, isBulk, isPremium) {

        let validationError;

        // Validate params
        const _validateParams = function () {

            const constraints = {

                to: function (value, attributes, attributeName, options, constraints) {
                    if (validate.isEmpty(value)) {
                        return {
                            presence: {message: "is required"}
                        };
                    }

                    if (!validate.isArray(value) && !validate.isString(value)) {
                        return {
                            format: "must be a string or an array strings (phone numbers)"
                        };
                    }

                    if (validate.isString(value)) {
                        let isInvalid = !/^\+\d{1,3}\d{3,}$/.test(value);
                        if (isInvalid) {
                            return {
                                format: "must be a valid phone number"
                            };
                        }
                    }

                    if (validate.isArray(value)) {
                        let foundInvalid = false;
                        value.forEach(function (phone) {
                            foundInvalid = !/^\+\d{1,3}\d{3,}$/.test(phone);
                        });
                        if (foundInvalid) {
                            return {
                                format: "must NOT contain invalid phone number"
                            }
                        }
                    }

                    return null;
                },

                from: {
                    isString: true
                },
                message: {
                    presence: true
                }
            };

            if (isBulk) {
                constraints.enqueue = {
                    inclusion: [true, false]
                };
            }

            if (isPremium) {
                constraints.keyword = {
                    presence: true,
                    isString: true
                };
                constraints.linkId = {
                    presence: false,
                    isString: true
                };
                constraints.retryDurationInHours = {
                    numericality: true
                }
            }

            const error = validate(params, constraints);
            if (error) {
                let msg = "";
                for (let k in error) {
                    msg += error[k] + "; ";
                }
                validationError = new Error(msg);
            }
        };

        _validateParams();

        // Multiple recipients?
        if (validate.isArray(params.to)) {
            if (params.to.length === 1) {
                params.to = params.to[0];
            } else {
                params.to = params.to.join();
            }
        }

        return new Promise(function (resolve, reject) {

            if (validationError) {
                return reject(validationError);
            }

            const body = {
                username: _self.options.username,
                to: params.to,
                message: params.message
            };

            if (params.from) {
                body.from = params.from
            }

            if (isBulk) {
                body.bulkSMSMode = 1;
                if (params.enqueue) {
                    body.enqueue = 1;
                }
                if (params.enqueue === false) {
                    body.enqueue = 0;
                }
            }

            if (isPremium) {
                body.bulkSMSMode = 0;
                body.keyword = params.keyword;
                body.linkId = params.linkId;
                if (params.retryDurationInHours) {
                    body.retryDurationInHours = params.retryDurationInHours;
                }
            }

            const url = isBulk
                ? Common.SMS_URL
                : Common.CONTENT_URL + '/messaging';

            const rq = unirest.post(url);
            rq.headers({
                apikey: _self.options.apiKey,
                Accept: _self.options.format
            });
            rq.send(body);
            rq.end(function (resp) {
                if (resp.status === 201) { // API returns CREATED on success!?
                    resolve(resp.body);
                } else {
                    reject(resp.body || resp.error);
                }
            });
        });
    };
}

SMS.prototype.send = function (params) {
    const opts = _.cloneDeep(params);
    return this._send(opts, true, false);
};

SMS.prototype.sendBulk = function (params) {
    return this.send(params);
};

SMS.prototype.sendPremium = function (params) {
    const opts = _.cloneDeep(params);
    return this._send(opts, false, true);
};

SMS.prototype.fetchMessages = function (params) {
    const _self = this;

    const opts = _.cloneDeep(params) || {};
    opts.lastReceivedId = opts.lastReceivedId || 0;

    return new Promise(function (resolve, reject) {

        const rq = unirest.get(Common.SMS_URL);
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.query({
            'username': _self.options.username,
            'lastReceivedId': opts.lastReceivedId
        });
        rq.end(function (resp) {
            if (resp.status === 200) {
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });
    });

};

SMS.prototype.createSubscription = function (params) {
	const _self = this;
    const opts = _.cloneDeep(params) || {};

    const constraints = {
        shortCode: {
            presence: true,
            isString: true
        },
        keyword: {
            presence: true,
            isString: true
        },
        phoneNumber: {
            presence: true,
            isString: true
        }
    };

    const validationError = validate(opts, constraints);

    const body = {
    	username: _self.options.username,
        shortCode: opts.shortCode,
        keyword: opts.keyword,
        phoneNumber: opts.phoneNumber
    };

    return new Promise(function (resolve, reject) {

        if (validationError) {
            return reject(validationError);
        }

        const rq = unirest.post(Common.CONTENT_URL + '/subscription/create');
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.send(body);
        rq.end(function (resp) {
            if (resp.status === 201) { // API returns CREATED on success!?
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });

    });

};

SMS.prototype.fetchSubscription = function (params) {
	const _self = this;
    const opts = _.cloneDeep(params) || {};

    const constraints = {
        shortCode: {
            presence: true,
            isString: true
        },
        keyword: {
            presence: true,
            isString: true
        },
        lastReceivedId: {
            numericality: true
        }
    };

    const validationError = validate(opts, constraints);

    opts.lastReceivedId = opts.lastReceivedId || 0;

    return new Promise(function (resolve, reject) {

        // throw validation error inside the promise chain
        if (validationError) {
            return reject(validationError);
        }

        const rq = unirest.get(Common.CONTENT_URL + '/subscription');
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.query({
            'username': _self.options.username,
            'lastReceivedId': opts.lastReceivedId,
            'keyword': opts.keyword,
            'shortCode': opts.shortCode
        });
        rq.end(function (resp) {
            if (resp.status === 200) {
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });

    });
};

SMS.prototype.deleteSubscription = function (params) {
    const _self = this;
    const options = _.cloneDeep(params);
    let validationError;

    const _validateParams = function () {
        const constraints = {
            shortCode: {
                presence: true,
                isString: true
            },
            keyword: {
                presence: true,
                isString: true
            },
            phoneNumber: {
                presence: true,
                isString: true
            }
        }

        const error = validate(options, constraints);

        const makeErrorMessage = function (error) {
            let msg = "";
            for (let k in error) {
                msg += error[k] + "; ";
            }
            validationError = new Error(msg);
        }

        if (error) {
            makeErrorMessage(error);
        }
    }

    _validateParams();

    return new Promise(function (resolve, reject) {
        if (validationError) {
            return reject(validationError);
        }

        const {
            username,
            apiKey,
            format
        } = _self.options;

        const {
            shortCode,
            keyword,
            phoneNumber
        } = options;

        const body = {
            username,
            shortCode,
            keyword,
            phoneNumber
        }

        const rq = unirest.post(Common.CONTENT_URL + '/subscription/delete');

        rq.headers({
            apiKey: apiKey,
            Accept: format,
            'Content-Type': 'application/x-www-form-urlencoded'
        });

        rq.send(body);

        rq.end(function(resp) {
            if (resp.status === 200) {
                // API deleted successfully
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        })

    });
}

module.exports = SMS;
<commit_msg>Fixed condition that checks if phone numbers in an array are valid<commit_after>'use strict';

const unirest = require('unirest');
const validate = require('validate.js');
const _ = require('lodash');

const Common = require('./common');

function SMS(options) {

    const _self = this;

    this.options = options;

    this._send = function (params, isBulk, isPremium) {

        let validationError;

        // Validate params
        const _validateParams = function () {
            const phoneNumberRegex = !/^\+\d{1,3}\d{3,}$/
            const constraints = {

                to: function (value, attributes, attributeName, options, constraints) {
                    if (validate.isEmpty(value)) {
                        return {
                            presence: {message: "is required"}
                        };
                    }

                    if (!validate.isArray(value) && !validate.isString(value)) {
                        return {
                            format: "must be a string or an array strings (phone numbers)"
                        };
                    }

                    if (validate.isString(value)) {
                        let isInvalid = phoneNumberRegex.test(value);
                        if (isInvalid) {
                            return {
                                format: "must be a valid phone number"
                            };
                        }
                    }

                    if (validate.isArray(value)) {
                        let phoneNumbersValidArr = [];
                        let invalidPhoneNumbers = []
                        value.forEach(function (phone) {
                            phoneNumbersValidArr.push(phoneNumberRegex.test(phone));
                        });
                        invalidPhoneNumbers = phoneNumbersValidArr.filter(phoneNumber => phoneNumber === false)
                        if (invalidPhoneNumbers.length > 0) {
                            return {
                                format: "must NOT contain invalid phone number"
                            }
                        }
                    }

                    return null;
                },

                from: {
                    isString: true
                },
                message: {
                    presence: true
                }
            };

            if (isBulk) {
                constraints.enqueue = {
                    inclusion: [true, false]
                };
            }

            if (isPremium) {
                constraints.keyword = {
                    presence: true,
                    isString: true
                };
                constraints.linkId = {
                    presence: false,
                    isString: true
                };
                constraints.retryDurationInHours = {
                    numericality: true
                }
            }

            const error = validate(params, constraints);
            if (error) {
                let msg = "";
                for (let k in error) {
                    msg += error[k] + "; ";
                }
                validationError = new Error(msg);
            }
        };

        _validateParams();

        // Multiple recipients?
        if (validate.isArray(params.to)) {
            params.to = params.to.join();
        }

        return new Promise(function (resolve, reject) {

            if (validationError) {
                return reject(validationError);
            }

            const body = {
                username: _self.options.username,
                to: params.to,
                message: params.message
            };

            if (params.from) {
                body.from = params.from
            }

            if (isBulk) {
                body.bulkSMSMode = 1;
                if (params.enqueue) {
                    body.enqueue = 1;
                }
                if (params.enqueue === false) {
                    body.enqueue = 0;
                }
            }

            if (isPremium) {
                body.bulkSMSMode = 0;
                body.keyword = params.keyword;
                body.linkId = params.linkId;
                if (params.retryDurationInHours) {
                    body.retryDurationInHours = params.retryDurationInHours;
                }
            }

            const url = isBulk
                ? Common.SMS_URL
                : Common.CONTENT_URL + '/messaging';

            const rq = unirest.post(url);
            rq.headers({
                apikey: _self.options.apiKey,
                Accept: _self.options.format
            });
            rq.send(body);
            rq.end(function (resp) {
                if (resp.status === 201) { // API returns CREATED on success!?
                    resolve(resp.body);
                } else {
                    reject(resp.body || resp.error);
                }
            });
        });
    };
}

SMS.prototype.send = function (params) {
    const opts = _.cloneDeep(params);
    return this._send(opts, true, false);
};

SMS.prototype.sendBulk = function (params) {
    return this.send(params);
};

SMS.prototype.sendPremium = function (params) {
    const opts = _.cloneDeep(params);
    return this._send(opts, false, true);
};

SMS.prototype.fetchMessages = function (params) {
    const _self = this;

    const opts = _.cloneDeep(params) || {};
    opts.lastReceivedId = opts.lastReceivedId || 0;

    return new Promise(function (resolve, reject) {

        const rq = unirest.get(Common.SMS_URL);
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.query({
            'username': _self.options.username,
            'lastReceivedId': opts.lastReceivedId
        });
        rq.end(function (resp) {
            if (resp.status === 200) {
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });
    });

};

SMS.prototype.createSubscription = function (params) {
	const _self = this;
    const opts = _.cloneDeep(params) || {};

    const constraints = {
        shortCode: {
            presence: true,
            isString: true
        },
        keyword: {
            presence: true,
            isString: true
        },
        phoneNumber: {
            presence: true,
            isString: true
        }
    };

    const validationError = validate(opts, constraints);

    const body = {
    	username: _self.options.username,
        shortCode: opts.shortCode,
        keyword: opts.keyword,
        phoneNumber: opts.phoneNumber
    };

    return new Promise(function (resolve, reject) {

        if (validationError) {
            return reject(validationError);
        }

        const rq = unirest.post(Common.CONTENT_URL + '/subscription/create');
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.send(body);
        rq.end(function (resp) {
            if (resp.status === 201) { // API returns CREATED on success!?
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });

    });

};

SMS.prototype.fetchSubscription = function (params) {
	const _self = this;
    const opts = _.cloneDeep(params) || {};

    const constraints = {
        shortCode: {
            presence: true,
            isString: true
        },
        keyword: {
            presence: true,
            isString: true
        },
        lastReceivedId: {
            numericality: true
        }
    };

    const validationError = validate(opts, constraints);

    opts.lastReceivedId = opts.lastReceivedId || 0;

    return new Promise(function (resolve, reject) {

        // throw validation error inside the promise chain
        if (validationError) {
            return reject(validationError);
        }

        const rq = unirest.get(Common.CONTENT_URL + '/subscription');
        rq.headers({
            apikey: _self.options.apiKey,
            Accept: _self.options.format
        });
        rq.query({
            'username': _self.options.username,
            'lastReceivedId': opts.lastReceivedId,
            'keyword': opts.keyword,
            'shortCode': opts.shortCode
        });
        rq.end(function (resp) {
            if (resp.status === 200) {
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        });

    });
};

SMS.prototype.deleteSubscription = function (params) {
    const _self = this;
    const options = _.cloneDeep(params);
    let validationError;

    const _validateParams = function () {
        const constraints = {
            shortCode: {
                presence: true,
                isString: true
            },
            keyword: {
                presence: true,
                isString: true
            },
            phoneNumber: {
                presence: true,
                isString: true
            }
        }

        const error = validate(options, constraints);

        const makeErrorMessage = function (error) {
            let msg = "";
            for (let k in error) {
                msg += error[k] + "; ";
            }
            validationError = new Error(msg);
        }

        if (error) {
            makeErrorMessage(error);
        }
    }

    _validateParams();

    return new Promise(function (resolve, reject) {
        if (validationError) {
            return reject(validationError);
        }

        const {
            username,
            apiKey,
            format
        } = _self.options;

        const {
            shortCode,
            keyword,
            phoneNumber
        } = options;

        const body = {
            username,
            shortCode,
            keyword,
            phoneNumber
        }

        const rq = unirest.post(Common.CONTENT_URL + '/subscription/delete');

        rq.headers({
            apiKey: apiKey,
            Accept: format,
            'Content-Type': 'application/x-www-form-urlencoded'
        });

        rq.send(body);

        rq.end(function(resp) {
            if (resp.status === 200) {
                // API deleted successfully
                resolve(resp.body);
            } else {
                reject(resp.body || resp.error);
            }
        })

    });
}

module.exports = SMS;
<|endoftext|>


<commit_before>import Papa from 'papaparse';

export default () => new Promise((resolve, reject) => {
    const fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('accept', '.csv, .tsv'); // parser auto-detects delimiter
    fileInput.onchange = e => {
        const file = e.target.files[0];
        Papa.parse(file, {
            header: false,
            complete: results => {
                document.body.removeChild(fileInput);
                resolve(results.data);
            },
            error: err => {
                document.body.removeChild(fileInput);
                reject(err);
            }
        });
    };
    document.body.appendChild(fileInput);
    fileInput.click();
});
<commit_msg>Add txt import so file can be imported/exported from symmetrically<commit_after>import Papa from 'papaparse';

export default () => new Promise((resolve, reject) => {
    const fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('accept', '.csv, .tsv, .txt'); // parser auto-detects delimiter
    fileInput.onchange = e => {
        const file = e.target.files[0];
        Papa.parse(file, {
            header: false,
            complete: results => {
                document.body.removeChild(fileInput);
                resolve(results.data);
            },
            error: err => {
                document.body.removeChild(fileInput);
                reject(err);
            }
        });
    };
    document.body.appendChild(fileInput);
    fileInput.click();
});
<|endoftext|>


<commit_before>/* konnector lib ready to be added to cozy-client-js */
import * as realtime from './realtime'
import * as jobs from './jobs'

export const KONNECTORS_DOCTYPE = 'io.cozy.konnectors'
export const KONNECTORS_RESULT_DOCTYPE = 'io.cozy.konnectors.result'

export const KONNECTOR_STATE = {
  // Available state
  AVAILABLE: 'available',
  INSTALLING: 'installing',
  UPGRADING: 'upgrading',
  UNINSTALLING: 'uninstalling',
  // Installed state, can be used to state that an application has been
  // installed but needs a user interaction to be activated and "ready".
  INSTALLED: 'installed',
  READY: 'ready'
}

export const KONNECTOR_RESULT_STATE = {
  ERRORED: 'errored',
  CONNECTED: 'done'
}

export const JOB_STATE = {
  READY: 'ready',
  ERRORED: 'errored',
  DONE: 'done'
}

function sanitizeSlug (konnector) {
  return konnector && { ...konnector, slug: konnector.slug || konnector.attributes.slug }
}

export function addAccount (cozy, konnector, account) {
  if (!konnector.accounts) konnector.accounts = []
  konnector.accounts.push(account)
  return Promise.resolve(konnector)
}

export function fetchManifest (cozy, source) {
  return source
    ? cozy.fetchJSON('GET', `/konnectors/manifests?Source=${encodeURIComponent(source)}`)
    : Promise.reject(new Error('Source konnector is unavailable'))
}

let cachedSlugIndex
function getSlugIndex (cozy) {
  return cachedSlugIndex
    ? Promise.resolve(cachedSlugIndex)
      : cozy.data.defineIndex(KONNECTORS_DOCTYPE, ['slug'])
          .then(index => {
            cachedSlugIndex = index
            return index
          })
}

export function findBySlug (cozy, slug) {
  if (!slug) throw new Error('Missing `slug` parameter')

  return getSlugIndex(cozy)
    .then(index => cozy.data.query(index, {selector: {slug: slug}}))
    .then(list => list.length ? list[0] : null)
    .then(konnector => sanitizeSlug(konnector))
}

export function unlinkFolder (cozy, konnector, folderId) {
  return !konnector._id
    /**
     * In case of a konnector set in the app and not in the platform,
     * there's no available `_id`. So we should returns an error, but here
     * it's just a warning, that doesn't implies anything, so we `resolve` and
     * not `reject`, because of what the next steps of deleting accounts will
     * fail.
     */
    ? Promise.resolve(new Error("konnector doesn't have available id"))
    : cozy.fetchJSON(
      'DELETE',
      `/data/io.cozy.konnectors/${encodeURIComponent(konnector._id)}/relationships/references`,
      {
        data: [
          {
            type: 'io.cozy.files',
            id: folderId
          }
        ]
      }
    )
    .then(() => deleteFolderPermission(cozy, konnector))
}

export function fetchResult (cozy, konnector) {
  if (!konnector) return Promise.reject(new Error('No konnector'))
  const slug = konnector.slug || konnector.attributes.slug
  return cozy.data.find(KONNECTORS_RESULT_DOCTYPE, slug)
}

export function findAll (cozy) {
  return findAllDocuments(cozy, KONNECTORS_DOCTYPE)
    .then(konnectors => konnectors.map(sanitizeSlug))
}

export function findAllResults (cozy) {
  return findAllDocuments(cozy, KONNECTORS_RESULT_DOCTYPE)
}

function findAllDocuments (cozy, doctype) {
  return cozy.fetchJSON('GET', `/data/${doctype}/_all_docs?include_docs=true`)
    .then(result => {
      return result.rows
        ? result.rows
          // filter documents only
          .filter(row => !row.doc.language)
          .map(row => row.doc)
        : []
    })
    .catch(error => {
      // the _all_docs endpoint returns a 404 error if no document with the given
      // doctype exists.
      if (error.status === 404) return []
      throw error
    })
}

export function subscribeAll (cozy) {
  return realtime.subscribeAll(cozy, KONNECTORS_DOCTYPE)
}

export function subscribeAllResults (cozy) {
  return realtime.subscribeAll(cozy, KONNECTORS_RESULT_DOCTYPE)
}

export function install (cozy, konnector, timeout = 120000) {
  ['slug', 'source'].forEach(property => {
    if (!konnector[property]) throw new Error(`Missing '${property}' property in konnector`)
  })

  const { slug, source } = konnector

  return findBySlug(cozy, slug)
    .catch(error => {
      if (error.status !== '404') throw error
      return null
    })
    .then(konnector => konnector
      // Need JSONAPI format
      ? cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        : cozy.fetchJSON('POST', `/konnectors/${slug}?Source=${encodeURIComponent(source)}`))
    .then(konnector => waitForKonnectorReady(cozy, konnector, timeout))
    .then(sanitizeSlug)
}

// monitor the status of the connector and resolve when the connector is ready
function waitForKonnectorReady (cozy, konnector, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Konnector installation timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        .then(konnectorResponse => {
          if (konnectorResponse.state === KONNECTOR_STATE.READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(konnector)
          }
        })
        .catch(error => {
          if (error.status === 404) return // keep waiting
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}

function patchFolderPermission (cozy, konnector, folderId = null) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  const saveFolder = folderId ? {type: 'io.cozy.files', values: [folderId]} : {}

  return cozy.fetchJSON('PATCH', `/permissions/konnectors/${encodeURIComponent(slug)}`, {
    data: {
      attributes: {
        permissions: {
          saveFolder: saveFolder
        }
      }
    }
  })
}

export function addFolderPermission (cozy, konnector, folderId) {
  return patchFolderPermission(cozy, konnector, folderId)
}

export function deleteFolderPermission (cozy, konnector) {
  return patchFolderPermission(cozy, konnector)
}

export function run (cozy, konnector, account) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  if (!slug) {
    throw new Error('Missing `slug` parameter for konnector')
  }
  if (!account._id) throw new Error('Missing `_id` parameter for account')

  const jobAttributes = {
    konnector: slug,
    account: account._id
  }

  if (account.folderId) {
    jobAttributes['folder_to_save'] = account.folderId
  }

  return cozy.jobs.create('konnector', jobAttributes, {
    priority: 10,
    max_exec_count: 1
  })
  .then(job => waitForJobFinished(cozy, job))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForJobFinished (cozy, job) {
  return new Promise((resolve, reject) => {
    jobs.subscribe(cozy, job)
      .then(subscription => {
        subscription.onUpdate(job => {
          if (job.state === JOB_STATE.ERRORED) {
            subscription.unsubscribe()
            reject(new Error(job.error))
          }

          if (job.state === JOB_STATE.DONE) {
            subscription.unsubscribe()
            resolve(job)
          }
        })
      })
      .catch(error => {
        reject(error)
      })
  })
}
<commit_msg>✨ feat: pass konnector parameters as URL parameters at konnector install<commit_after>/* konnector lib ready to be added to cozy-client-js */
import * as realtime from './realtime'
import * as jobs from './jobs'

export const KONNECTORS_DOCTYPE = 'io.cozy.konnectors'
export const KONNECTORS_RESULT_DOCTYPE = 'io.cozy.konnectors.result'

export const KONNECTOR_STATE = {
  // Available state
  AVAILABLE: 'available',
  INSTALLING: 'installing',
  UPGRADING: 'upgrading',
  UNINSTALLING: 'uninstalling',
  // Installed state, can be used to state that an application has been
  // installed but needs a user interaction to be activated and "ready".
  INSTALLED: 'installed',
  READY: 'ready'
}

export const KONNECTOR_RESULT_STATE = {
  ERRORED: 'errored',
  CONNECTED: 'done'
}

export const JOB_STATE = {
  READY: 'ready',
  ERRORED: 'errored',
  DONE: 'done'
}

function sanitizeSlug (konnector) {
  return konnector && { ...konnector, slug: konnector.slug || konnector.attributes.slug }
}

export function addAccount (cozy, konnector, account) {
  if (!konnector.accounts) konnector.accounts = []
  konnector.accounts.push(account)
  return Promise.resolve(konnector)
}

export function fetchManifest (cozy, source) {
  return source
    ? cozy.fetchJSON('GET', `/konnectors/manifests?Source=${encodeURIComponent(source)}`)
    : Promise.reject(new Error('Source konnector is unavailable'))
}

let cachedSlugIndex
function getSlugIndex (cozy) {
  return cachedSlugIndex
    ? Promise.resolve(cachedSlugIndex)
      : cozy.data.defineIndex(KONNECTORS_DOCTYPE, ['slug'])
          .then(index => {
            cachedSlugIndex = index
            return index
          })
}

export function findBySlug (cozy, slug) {
  if (!slug) throw new Error('Missing `slug` parameter')

  return getSlugIndex(cozy)
    .then(index => cozy.data.query(index, {selector: {slug: slug}}))
    .then(list => list.length ? list[0] : null)
    .then(konnector => sanitizeSlug(konnector))
}

export function unlinkFolder (cozy, konnector, folderId) {
  return !konnector._id
    /**
     * In case of a konnector set in the app and not in the platform,
     * there's no available `_id`. So we should returns an error, but here
     * it's just a warning, that doesn't implies anything, so we `resolve` and
     * not `reject`, because of what the next steps of deleting accounts will
     * fail.
     */
    ? Promise.resolve(new Error("konnector doesn't have available id"))
    : cozy.fetchJSON(
      'DELETE',
      `/data/io.cozy.konnectors/${encodeURIComponent(konnector._id)}/relationships/references`,
      {
        data: [
          {
            type: 'io.cozy.files',
            id: folderId
          }
        ]
      }
    )
    .then(() => deleteFolderPermission(cozy, konnector))
}

export function fetchResult (cozy, konnector) {
  if (!konnector) return Promise.reject(new Error('No konnector'))
  const slug = konnector.slug || konnector.attributes.slug
  return cozy.data.find(KONNECTORS_RESULT_DOCTYPE, slug)
}

export function findAll (cozy) {
  return findAllDocuments(cozy, KONNECTORS_DOCTYPE)
    .then(konnectors => konnectors.map(sanitizeSlug))
}

export function findAllResults (cozy) {
  return findAllDocuments(cozy, KONNECTORS_RESULT_DOCTYPE)
}

function findAllDocuments (cozy, doctype) {
  return cozy.fetchJSON('GET', `/data/${doctype}/_all_docs?include_docs=true`)
    .then(result => {
      return result.rows
        ? result.rows
          // filter documents only
          .filter(row => !row.doc.language)
          .map(row => row.doc)
        : []
    })
    .catch(error => {
      // the _all_docs endpoint returns a 404 error if no document with the given
      // doctype exists.
      if (error.status === 404) return []
      throw error
    })
}

export function subscribeAll (cozy) {
  return realtime.subscribeAll(cozy, KONNECTORS_DOCTYPE)
}

export function subscribeAllResults (cozy) {
  return realtime.subscribeAll(cozy, KONNECTORS_RESULT_DOCTYPE)
}

export function install (cozy, konnector, timeout = 120000) {
  ['slug', 'source'].forEach(property => {
    if (!konnector[property]) throw new Error(`Missing '${property}' property in konnector`)
  })

  const { slug, source, parameters } = konnector
  let urlParams = `Source=${encodeURIComponent(source)}`

  // While the registry is not there, the parameters are passed at konnectors install
  if (parameters) {
    urlParams = urlParams + `&Parameters=${encodeURIComponent(JSON.stringify(parameters))}`
  }

  return findBySlug(cozy, slug)
    .catch(error => {
      if (error.status !== '404') throw error
      return null
    })
    .then(konnector => konnector
      // Need JSONAPI format
      ? cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        : cozy.fetchJSON('POST', `/konnectors/${slug}?${urlParams}`))
    .then(konnector => waitForKonnectorReady(cozy, konnector, timeout))
    .then(sanitizeSlug)
}

// monitor the status of the connector and resolve when the connector is ready
function waitForKonnectorReady (cozy, konnector, timeout) {
  return new Promise((resolve, reject) => {
    const idTimeout = setTimeout(() => {
      reject(new Error('Konnector installation timed out'))
    }, timeout)

    const idInterval = setInterval(() => {
      cozy.data.find(KONNECTORS_DOCTYPE, konnector._id)
        .then(konnectorResponse => {
          if (konnectorResponse.state === KONNECTOR_STATE.READY) {
            clearTimeout(idTimeout)
            clearInterval(idInterval)
            resolve(konnector)
          }
        })
        .catch(error => {
          if (error.status === 404) return // keep waiting
          clearTimeout(idTimeout)
          clearInterval(idInterval)
          reject(error)
        })
    }, 1000)
  })
}

function patchFolderPermission (cozy, konnector, folderId = null) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  const saveFolder = folderId ? {type: 'io.cozy.files', values: [folderId]} : {}

  return cozy.fetchJSON('PATCH', `/permissions/konnectors/${encodeURIComponent(slug)}`, {
    data: {
      attributes: {
        permissions: {
          saveFolder: saveFolder
        }
      }
    }
  })
}

export function addFolderPermission (cozy, konnector, folderId) {
  return patchFolderPermission(cozy, konnector, folderId)
}

export function deleteFolderPermission (cozy, konnector) {
  return patchFolderPermission(cozy, konnector)
}

export function run (cozy, konnector, account) {
  const slug = konnector.attributes ? konnector.attributes.slug : konnector.slug
  if (!slug) {
    throw new Error('Missing `slug` parameter for konnector')
  }
  if (!account._id) throw new Error('Missing `_id` parameter for account')

  const jobAttributes = {
    konnector: slug,
    account: account._id
  }

  if (account.folderId) {
    jobAttributes['folder_to_save'] = account.folderId
  }

  return cozy.jobs.create('konnector', jobAttributes, {
    priority: 10,
    max_exec_count: 1
  })
  .then(job => waitForJobFinished(cozy, job))
}

// monitor the status of the connector and resolve when the connector is ready
function waitForJobFinished (cozy, job) {
  return new Promise((resolve, reject) => {
    jobs.subscribe(cozy, job)
      .then(subscription => {
        subscription.onUpdate(job => {
          if (job.state === JOB_STATE.ERRORED) {
            subscription.unsubscribe()
            reject(new Error(job.error))
          }

          if (job.state === JOB_STATE.DONE) {
            subscription.unsubscribe()
            resolve(job)
          }
        })
      })
      .catch(error => {
        reject(error)
      })
  })
}
<|endoftext|>


<commit_before>module.exports = Math.clz32 || function clz32 (value) {
    "use strict";

    value = Number(value) >>> 0;
    return value ? 32 - value.toString(2).length : 32;
};
<commit_msg>remove unnecessary Number() in clz32<commit_after>module.exports = Math.clz32 || function clz32 (value) {
    "use strict";

    value = value >>> 0;
    return value ? 32 - value.toString(2).length : 32;
};
<|endoftext|>


<commit_before>
var selectionDate = function (onChange, months, optionDay, optionMonth, optionYear) {

	var _day, _month, _year, _i, _currentYear;

	_day = document.getElementById('sdate-day'),
	_month = document.getElementById('sdate-month'),
	_year = document.getElementById('sdate-year');

	optionDay = optionDay || 'Day';
	optionMonth = optionMonth || 'Month';
	optionYear = optionYear || 'Year';
	_currentYear = new Date().getFullYear();

	// validations

	if (!onChange) throw Exception('Argument "onChange" not found!');
	if (typeof onChange !== 'function') throw Exception('Argument "onChange" not a function!');
	if (!months) throw Exception('Argument "months" not found!');
	if (!(typeof months === 'object')
		&& months.hasOwnProperty('length')) throw Exception('Argument "months" not an array!');
	if (months.length !== 12) throw Exception('Length of argument "months" not equals 12');

	if (!_day) throw Exception('Page not contains the element with id="sdate-day"');
	if (!_month) throw Exception('Page not contains the element with id="sdate-month"');
	if (!_year) throw Exception('Page not contains the element with id="sdate-year"');

	// fill selects

	_day.appendChild(createFirstOption('dp_day', optionDay));
	_month.appendChild(createFirstOption('dp_month', optionMonth));
	_year.appendChild(createFirstOption('dp_year', optionYear));

	for (_i = 1; _i <= 31; ++_i) {
		_day.appendChild(createOption(_i, _i));
	}
	for (_i = 0; _i < 12; ++_i) {
		_month.appendChild(createOption(_i + 1, months[_i]));
	}
	for (_i = _currentYear; _i > 1930; --_i) {
		_year.appendChild(createOption(_i, _i));
	}

	// set hander

	_day.onchange = _month.onchange = _year.onchange = function (e) {
		if (_day.value > 0 && _month.value > 0 && _year.value > 0) {
			onChange(new Date(_year.value, _month.value, _day.value));
		}
	}

	// export

	return {
		setDate: function (date) {
			_day.selectedIndex = date.getDate();
			_month.selectedIndex = date.getMonth() + 1;
			_year.selectedIndex = (_currentYear - date.getFullYear()) + 1;

			onChange(date);
		}
	};

	// private helper functions

	function createFirstOption(id, text) {
		var o = createOption('0', text);
		o.id = id;
		o.disabled = true;
		o.selected = true;
		return o;
	}

	function createOption(value, text) {
		var o = document.createElement('option');
		o.value = value;
		o.textContent = text;
		return o;
	}

	function Exception (message) {
		return new Error('[selectionDate] ' + message);
	}

};<commit_msg>Update sdate.js<commit_after>
/**
 * Function for generate date picker.
 *
 * @author https://github.com/wcoder
 * @version 1.0.0
 *
 * @param [callback] onChange - function to get the value of date
 * @param [array] months - array of month names
 * @param [string] optionDay - default value for select day
 * @param [string] optionMonth - default value for select month
 * @param [string] optionYear - default value for select year
 */
var selectionDate = function (onChange, months, optionDay, optionMonth, optionYear) {

	var _day, _month, _year, _i, _currentYear;

	_day = document.getElementById('sdate-day'),
	_month = document.getElementById('sdate-month'),
	_year = document.getElementById('sdate-year');

	optionDay = optionDay || 'Day';
	optionMonth = optionMonth || 'Month';
	optionYear = optionYear || 'Year';
	_currentYear = new Date().getFullYear();

	// validations

	if (!onChange) throw Exception('Argument "onChange" not found!');
	if (typeof onChange !== 'function') throw Exception('Argument "onChange" not a function!');
	if (!months) throw Exception('Argument "months" not found!');
	if (!(typeof months === 'object')
		&& months.hasOwnProperty('length')) throw Exception('Argument "months" not an array!');
	if (months.length !== 12) throw Exception('Length of argument "months" not equals 12');

	if (!_day) throw Exception('Page not contains the element with id="sdate-day"');
	if (!_month) throw Exception('Page not contains the element with id="sdate-month"');
	if (!_year) throw Exception('Page not contains the element with id="sdate-year"');

	// fill selects

	_day.appendChild(createFirstOption('dp_day', optionDay));
	_month.appendChild(createFirstOption('dp_month', optionMonth));
	_year.appendChild(createFirstOption('dp_year', optionYear));

	for (_i = 1; _i <= 31; ++_i) {
		_day.appendChild(createOption(_i, _i));
	}
	for (_i = 0; _i < 12; ++_i) {
		_month.appendChild(createOption(_i + 1, months[_i]));
	}
	for (_i = _currentYear; _i > 1930; --_i) {
		_year.appendChild(createOption(_i, _i));
	}

	// set hander

	_day.onchange = _month.onchange = _year.onchange = function (e) {
		if (_day.value > 0 && _month.value > 0 && _year.value > 0) {
			onChange(new Date(_year.value, _month.value, _day.value));
		}
	}

	// export

	return {
		setDate: function (date) {
			_day.selectedIndex = date.getDate();
			_month.selectedIndex = date.getMonth() + 1;
			_year.selectedIndex = (_currentYear - date.getFullYear()) + 1;

			onChange(date);
		}
	};

	// private helper functions

	function createFirstOption(id, text) {
		var o = createOption('0', text);
		o.id = id;
		o.disabled = true;
		o.selected = true;
		return o;
	}

	function createOption(value, text) {
		var o = document.createElement('option');
		o.value = value;
		o.textContent = text;
		return o;
	}

	function Exception (message) {
		return new Error('[selectionDate] ' + message);
	}

};
<|endoftext|>


<commit_before>(function () {
    var sysClass = function (opts,mailer,storage) {
        this.sys = opts;
        this.sys.mailer = mailer;
        this.sys.fs = require('fs');
        this.sys.marked = require('marked');
        this.sys.async = require('async');
        this.sys.pandoc = require('pandoc');
        this.sys.multiparty = require('multiparty')
    };
    sysClass.prototype.getSys = function () {
        function validAdmin (params) {
            return params.admin
                && this.admin[params.admin]
                && this.admin[params.admin].role
                && this.admin[params.admin].role == 1
                ? this.admin[params.admin].name : false
        };
        function validCommenter (params) {
            return params.commenter
                && this.admin[params.commenter]
                && this.admin[params.commenter].role
                && this.admin[params.commenter].role === 2
                ? this.admin[params.commenter] : false
        };
        function markdown (txt,pandocPrep) {
            if (!txt) return '

';
            txt = txt.replace(/(:-?\))/g,'(\u0298\u203f\u0298)');
            txt = txt.replace(/(:-\/)/g,'_(\u0361\u0e4f\u032f\u0361\u0e4f)_');
            txt = txt.replace(/^>>>\s+(.*)/,'__Rule\u21b4__\n\n>>> $1');
            txt = txt.replace(/^>>\s+(.*)/,'__Pattern\u21b4__\n\n>> $1');
            txt = txt.replace(/^>\s+(.*)/,'__Explanation\u21b4__\n\n> $1');
            txt = txt.replace(/\(\(([a-zA-Z1-9])\s+(.*?)\)\)/g, '(($1)) $2');
            txt = txt.replace(/([\uff01-\uff5a])/g, function (aChar) {
                val = (aChar.charCodeAt(0) - 65248);
                return String.fromCharCode(val);
            });
            if (pandocPrep) {
                txt = txt.replace(/
/g,'{newline}');
            }
            txt = txt.replace(/([\u0456\u0405\u0455\u0408\u0410\u0430\u0412\u041e\u043e\u0420\u0421\u0441\u0422\u0423\u0443\u042c\u0435])/g, function (aChar) {
                switch (aChar) {
                case '\u0456':
                    aChar = 'i';
                    break;
                case '\u0405':
                    aChar = 'S';
                    break;
                case '\u0455':
                    aChar = 's';
                    break;
                case '\u0408':
                    aChar = 'J';
                    break;
                case '\u0458':
                    aChar = 'j';
                    break;
                case '\u0410':
                    aChar = 'A';
                    break;
                case '\u0430':
                    aChar = 'a';
                    break;
                case '\u0412':
                    aChar = 'B';
                    break;
                case '\u041e':
                    aChar = 'O';
                    break;
                case '\u043e':
                    aChar = 'o';
                    break;
                case '\u0420':
                    aChar = 'P';
                    break;
                case '\u0421':
                    aChar = 'C';
                    break;
                case '\u0441':
                    aChar = 'c';
                    break;
                case '\u0422':
                    aChar = 'T';
                    break;
                case '\u0423':
                    aChar = 'y';
                    break;
                case '\u0443':
                    aChar = 'y';
                    break;
                case '\u042c':
                    aChar = 'b';
                    break;
                case '\u0435':
                    aChar = 'e';
                    break;
                }
                return aChar;
            });
            return this.marked(txt);
        };
        function randomize(array) {
            console.log("RUN: randomize()");
            var new_order = [];
            var currentIndex = array.length;
            var temporaryValue;
            var randomIndex;
            var temporaryPos;

            for (var i=0,ilen=array.length;i<ilen;i+=1) {
                new_order.push(i);
            }

            // While there remain elements to shuffle...
            while (0 !== currentIndex) {
                // Pick a remaining element...
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                // Swap it with the current element.
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
                // Same treatment for the sequence map
                temporaryPos = new_order[currentIndex];
                new_order[currentIndex] = new_order[randomIndex];
                new_order[randomIndex] = temporaryPos;
            }
            var remap = {};
            for (var i=0,ilen=array.length;i<ilen;i+=1) {
                remap[i] = new_order[i];
            }
            return remap;
        }
        this.sys.spawn = require('child_process').spawn;
        this.sys.markdown = markdown;
        this.sys.validAdmin = validAdmin;
        this.sys.validCommenter = validCommenter;
        this.sys.randomize = randomize;
        return this.sys;
    };
    exports.sysClass = sysClass;
})();
<commit_msg>Math rendering for exams (take 1)<commit_after>(function () {
    var sysClass = function (opts,mailer,storage) {
        this.sys = opts;
        this.sys.mailer = mailer;
        this.sys.fs = require('fs');
        this.sys.marked = require('marked');
        this.sys.async = require('async');
        this.sys.pandoc = require('pandoc');
        this.sys.multiparty = require('multiparty')
    };
    sysClass.prototype.getSys = function () {
        function validAdmin (params) {
            return params.admin
                && this.admin[params.admin]
                && this.admin[params.admin].role
                && this.admin[params.admin].role == 1
                ? this.admin[params.admin].name : false
        };
        function validCommenter (params) {
            return params.commenter
                && this.admin[params.commenter]
                && this.admin[params.commenter].role
                && this.admin[params.commenter].role === 2
                ? this.admin[params.commenter] : false
        };
        function markdown (txt,pandocPrep) {
            if (!txt) return '

';
            txt = txt.replace(/(:-?\))/g,'(\u0298\u203f\u0298)');
            txt = txt.replace(/(:-\/)/g,'_(\u0361\u0e4f\u032f\u0361\u0e4f)_');
            txt = txt.replace(/^>>>\s+(.*)/,'__Rule\u21b4__\n\n>>> $1');
            txt = txt.replace(/^>>\s+(.*)/,'__Pattern\u21b4__\n\n>> $1');
            txt = txt.replace(/^>\s+(.*)/,'__Explanation\u21b4__\n\n> $1');
            txt = txt.replace(/\(\(([a-zA-Z1-9])\s+(.*?)\)\)/g, '(($1)) $2');
            txt = txt.replace(/([\uff01-\uff5a])/g, function (aChar) {
                val = (aChar.charCodeAt(0) - 65248);
                return String.fromCharCode(val);
            });
            if (pandocPrep) {
                txt = txt.replace(/
/g,'{newline}');
                txt = txt.replace(/\\(/g,'$');
                txt = txt.replace(/\\)/g,'$');
            }
            txt = txt.replace(/([\u0456\u0405\u0455\u0408\u0410\u0430\u0412\u041e\u043e\u0420\u0421\u0441\u0422\u0423\u0443\u042c\u0435])/g, function (aChar) {
                switch (aChar) {
                case '\u0456':
                    aChar = 'i';
                    break;
                case '\u0405':
                    aChar = 'S';
                    break;
                case '\u0455':
                    aChar = 's';
                    break;
                case '\u0408':
                    aChar = 'J';
                    break;
                case '\u0458':
                    aChar = 'j';
                    break;
                case '\u0410':
                    aChar = 'A';
                    break;
                case '\u0430':
                    aChar = 'a';
                    break;
                case '\u0412':
                    aChar = 'B';
                    break;
                case '\u041e':
                    aChar = 'O';
                    break;
                case '\u043e':
                    aChar = 'o';
                    break;
                case '\u0420':
                    aChar = 'P';
                    break;
                case '\u0421':
                    aChar = 'C';
                    break;
                case '\u0441':
                    aChar = 'c';
                    break;
                case '\u0422':
                    aChar = 'T';
                    break;
                case '\u0423':
                    aChar = 'y';
                    break;
                case '\u0443':
                    aChar = 'y';
                    break;
                case '\u042c':
                    aChar = 'b';
                    break;
                case '\u0435':
                    aChar = 'e';
                    break;
                }
                return aChar;
            });
            return this.marked(txt);
        };
        function randomize(array) {
            console.log("RUN: randomize()");
            var new_order = [];
            var currentIndex = array.length;
            var temporaryValue;
            var randomIndex;
            var temporaryPos;

            for (var i=0,ilen=array.length;i<ilen;i+=1) {
                new_order.push(i);
            }

            // While there remain elements to shuffle...
            while (0 !== currentIndex) {
                // Pick a remaining element...
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                // Swap it with the current element.
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
                // Same treatment for the sequence map
                temporaryPos = new_order[currentIndex];
                new_order[currentIndex] = new_order[randomIndex];
                new_order[randomIndex] = temporaryPos;
            }
            var remap = {};
            for (var i=0,ilen=array.length;i<ilen;i+=1) {
                remap[i] = new_order[i];
            }
            return remap;
        }
        this.sys.spawn = require('child_process').spawn;
        this.sys.markdown = markdown;
        this.sys.validAdmin = validAdmin;
        this.sys.validCommenter = validCommenter;
        this.sys.randomize = randomize;
        return this.sys;
    };
    exports.sysClass = sysClass;
})();
<|endoftext|>


<commit_before>'use strict';

// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.computeCrimePercentages = functions.database.ref('crimedata/{pushId}/offense_code_group')
  .onWrite(event => {
    console.log('Recalculating crime percentages for new message', event.params.pushId);
    // Only edit data when it is first created.
    if (!event.data.changed()) {
      console.log('Skipping update to same value');
      return;
    }

    console.log("Processing code group write:", event.data.val());
    var ocgRef = admin.database().ref('analytics/offense_code_group/');
    return ocgRef.transaction(current_value => {
      if (current_value[event.data.val()]) {
        console.log("Incrementing", event.data.val());
        current_value[event.data.val()]+= 1;
      } else {
        console.log("Incrementing", event.data.val());
        current_value[event.data.val()] = 1;
      }

      if (event.data.previous.exists()) {
        if (current_value[event.data.previous.val()]) {
          console.log("Decrementing", event.data.previous.val());
          current_value[event.data.previous.val()]-= 1;
          if (current_value[event.data.previous.val()] <= 0) {
            delete current_value[event.data.previous.val()];
          }
        }
      }

      return current_value;
    }).then(() => console.log("Transaction committed"));
  });

exports.computeSeverityLevel = functions.database.ref('crimedata/{pushId}/offense_code_group')
  .onWrite(event => {
    console.log('Calculating severity level for', event.params.pushId);

    return event.data.ref.parent.child('severity').set("Something");
  });

<commit_msg>asdfasd<commit_after>'use strict';

// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);

exports.computeCrimePercentages = functions.database.ref('crimedata/{pushId}/offense_code_group')
  .onWrite(event => {
    console.log('Recalculating crime percentages for new message', event.params.pushId);
    // Only edit data when it is first created.
    if (!event.data.changed()) {
      console.log('Skipping update to same value');
      return;
    }

    console.log("Processing code group write:", event.data.val());
    var ocgRef = admin.database().ref('analytics/offense_code_group');
    return ocgRef.transaction(current_value => {
      if (current_value[event.data.val()]) {
        console.log("Incrementing", event.data.val());
        current_value[event.data.val()]+= 1;
      } else {
        console.log("Incrementing", event.data.val());
        current_value[event.data.val()] = 1;
      }

      if (event.data.previous.exists()) {
        if (current_value[event.data.previous.val()]) {
          console.log("Decrementing", event.data.previous.val());
          current_value[event.data.previous.val()]-= 1;
          if (current_value[event.data.previous.val()] <= 0) {
            delete current_value[event.data.previous.val()];
          }
        }
      }

      return current_value;
    }).then(() => console.log("Transaction committed"));
  });

exports.computeSeverityLevel = functions.database.ref('crimedata/{pushId}/offense_code_group')
  .onWrite(event => {
    console.log('Calculating severity level for', event.params.pushId);

    return event.data.ref.parent.child('severity').set("Something");
  });

<|endoftext|>


<commit_before>"use strict";

/*jshint node:true strict: true */

var Rule = require(__dirname + '/rule.js');

/**
 * tld library
 *
 * Useable methods are those documented with an @api in JSDoc
 * See README.md for more explainations on how to use this stuff.
 */
function tld () {
  /*jshint validthis: true */
  this.rules = [];
}

tld.init = function init () {
  return new tld();
};

/**
 * Returns the best rule for a given host based on candidates
 *
 * @static
 * @param host {String} Hostname to check rules against
 * @param rules {Array} List of rules used to work on
 * @return {Object} Candidate object, with a normal and exception state
 */
tld.getCandidateRule = function getCandidateRule (host, rules) {
  var rule = {'normal': null, 'exception': null};

  rules.some(function (r) {
    var pattern;

    //sld matching? escape the loop immediately (except if it's an exception)
    if ('.' + host === r.getNormalXld()) {
      if (r.exception === true) {
        rule.normal = r;
      }

      return true;
    }

    //otherwise check as a complete host
    //if it's an exception, we want to loop a bit more to a normal rule
    pattern = '.+' + r.getNormalPattern() + '$';

    if ((new RegExp(pattern)).test(host)) {
      rule[r.exception ? 'exception' : 'normal'] = r;
      return !r.exception;
    }

    return false;
  });

  //if there is an exception, we challenge its rules without changing pattern
  if (rule.normal && rule.exception) {
    rule.normal.wildcard = rule.exception.wildcard;
  }

  return rule.normal;
};

/**
 * Retrieve a subset of rules for a Top-Level-Domain string
 *
 * @param tld {String} Top-Level-Domain string
 * @return {Array} Rules subset
 */
tld.prototype.getRulesForTld = function getRulesForTld (tld, default_rule) {
  var exception = '!';
  var wildcard = '*';
  var append_tld_rule = true;
  var rules = this.rules[tld];

  // Already parsed
  if (Array.isArray(rules)) {
    return rules;
  }

  // Nothing found, apply some default value
  if (!rules) {
    return default_rule ? [ default_rule ] : [];
  }

  // Parsing needed
  rules = rules.split('|').map(function transformAsRule (sld) {
    var first_bit = sld[0];

    if (first_bit === exception || first_bit === wildcard) {
      sld = sld.slice(1);

      if (!sld) {
        append_tld_rule = false;
      }
    }

    return new Rule({
      "firstLevel":  tld,
      "secondLevel": sld,
      "exception":   first_bit === exception,
      "wildcard":    first_bit === wildcard
    });
  });

  // Always prepend to make it the latest rule to be applied
  if (append_tld_rule) {
    rules.unshift(new Rule({
      "firstLevel": tld
    }));
  }

  this.rules[tld] = rules.reverse();

  return rules;
};

/**
 * Checks if the TLD exists for a given host
 *
 * @api
 * @param {string} host
 * @return {boolean}
 */
tld.prototype.tldExists = function tldExists(host){
  var hostTld;

  host = (host+'').trim().toLocaleLowerCase();

  // Easy case, it's a TLD
  if (this.rules[host]){
    return true;
  }

  // Popping only the TLD of the hostname
  hostTld = host.split('.').pop();

  return this.rules[hostTld] !== undefined;
};

/**
 * Detects the domain based on rules and upon and a host string
 *
 * @api
 * @param {string} host
 * @return {String}
 */
tld.prototype.getDomain = function getDomain (host) {
  var domain = null, hostTld, rules, rule;

  if (this.isValid(host) === false) {
    return null;
  }

  host = host.toLowerCase();
  hostTld = host.split('.').pop();
  rules = this.getRulesForTld(hostTld, new Rule({"firstLevel": hostTld}));
  rule = tld.getCandidateRule(host, rules);

  if (rule === null) {
    return null;
  }

  host.replace(new RegExp(rule.getPattern()), function (m, d) {
    domain = d;
  });

  return domain;
};

/**
 * Returns the subdomain of a host string
 *
 * @api
 * @param {string} host
 * @return {string|null} a subdomain string if any, blank string if subdomain is empty, otherwise null
 */
tld.prototype.getSubdomain = function getSubdomain(host){

};

/**
 * Checking if a host string is valid
 * It's usually a preliminary check before trying to use getDomain or anything else
 *
 * Beware: it does not check if the TLD exists.
 *
 * @api
 * @todo handle localhost, local etc.
 * @param host {String}
 * @return {Boolean}
 */
tld.prototype.isValid = function isValid (host) {
  return !(typeof host !== 'string' || host.indexOf('.') === -1 || host[0] === '.');
};

module.exports = tld;
<commit_msg>Small refactoring<commit_after>"use strict";

/*jshint node:true strict: true */

var Rule = require(__dirname + '/rule.js');

/**
 * tld library
 *
 * Useable methods are those documented with an @api in JSDoc
 * See README.md for more explainations on how to use this stuff.
 */
function tld () {
  /*jshint validthis: true */
  this.rules = [];
}

tld.init = function init () {
  return new tld();
};

/**
 * Returns the best rule for a given host based on candidates
 *
 * @static
 * @param host {String} Hostname to check rules against
 * @param rules {Array} List of rules used to work on
 * @return {Object} Candidate object, with a normal and exception state
 */
tld.getCandidateRule = function getCandidateRule (host, rules) {
  var rule = {'normal': null, 'exception': null};

  rules.some(function (r) {
    var pattern;

    //sld matching? escape the loop immediately (except if it's an exception)
    if ('.' + host === r.getNormalXld()) {
      if (r.exception === true) {
        rule.normal = r;
      }

      return true;
    }

    //otherwise check as a complete host
    //if it's an exception, we want to loop a bit more to a normal rule
    pattern = '.+' + r.getNormalPattern() + '$';

    if ((new RegExp(pattern)).test(host)) {
      rule[r.exception ? 'exception' : 'normal'] = r;
      return !r.exception;
    }

    return false;
  });

  //if there is an exception, we challenge its rules without changing pattern
  if (rule.normal && rule.exception) {
    rule.normal.wildcard = rule.exception.wildcard;
  }

  return rule.normal;
};

/**
 * Retrieve a subset of rules for a Top-Level-Domain string
 *
 * @param tld {String} Top-Level-Domain string
 * @return {Array} Rules subset
 */
tld.prototype.getRulesForTld = function getRulesForTld (tld, default_rule) {
  var exception = '!';
  var wildcard = '*';
  var append_tld_rule = true;
  var rules = this.rules[tld];

  // Already parsed
  if (Array.isArray(rules)) {
    return rules;
  }

  // Nothing found, apply some default value
  if (!rules) {
    return default_rule ? [ default_rule ] : [];
  }

  // Parsing needed
  rules = rules.split('|').map(function transformAsRule (sld) {
    var first_bit = sld[0];

    if (first_bit === exception || first_bit === wildcard) {
      sld = sld.slice(1);

      if (!sld) {
        append_tld_rule = false;
      }
    }

    return new Rule({
      "firstLevel":  tld,
      "secondLevel": sld,
      "exception":   first_bit === exception,
      "wildcard":    first_bit === wildcard
    });
  });

  // Always prepend to make it the latest rule to be applied
  if (append_tld_rule) {
    rules.unshift(new Rule({
      "firstLevel": tld
    }));
  }

  this.rules[tld] = rules.reverse();

  return rules;
};

/**
 * Checks if the TLD exists for a given host
 *
 * @api
 * @param {string} host
 * @return {boolean}
 */
tld.prototype.tldExists = function tldExists(host){
  var hostTld;

  host = (host+'').trim().toLocaleLowerCase();

  // Easy case, it's a TLD
  if (this.rules[host]){
    return true;
  }

  // Popping only the TLD of the hostname
  hostTld = host.split('.').pop();

  return this.rules[hostTld] !== undefined;
};

/**
 * Detects the domain based on rules and upon and a host string
 *
 * @api
 * @param {string} host
 * @return {String}
 */
tld.prototype.getDomain = function getDomain (host) {
  var domain = null, hostTld, rules, rule;

  if (this.isValid(host) === false) {
    return null;
  }

  host = tld.cleanHostValue(host);
  hostTld = tld.extractTldFromHost(host);
  rules = this.getRulesForTld(hostTld, new Rule({"firstLevel": hostTld}));
  rule = tld.getCandidateRule(host, rules);

  if (rule === null) {
    return null;
  }

  host.replace(new RegExp(rule.getPattern()), function (m, d) {
    domain = d;
  });

  return domain;
};

/**
 * Returns the subdomain of a host string
 *
 * @api
 * @param {string} host
 * @return {string|null} a subdomain string if any, blank string if subdomain is empty, otherwise null
 */
tld.prototype.getSubdomain = function getSubdomain(host){

};

/**
 * Checking if a host string is valid
 * It's usually a preliminary check before trying to use getDomain or anything else
 *
 * Beware: it does not check if the TLD exists.
 *
 * @api
 * @todo handle localhost, local etc.
 * @param host {String}
 * @return {Boolean}
 */
tld.prototype.isValid = function isValid (host) {
  return !(typeof host !== 'string' || host.indexOf('.') === -1 || host[0] === '.');
};

/**
 * Utility to cleanup the base host value
 *
 * @param {string} host
 * @return {String}
 */
tld.cleanHostValue = function cleanHostValue(host){
  return (host+'').trim().toLowerCase();
};

/**
 * Utility to extract the TLD from a host string
 *
 * @param {string} host
 * @return {String}
 */
tld.extractTldFromHost = function extractTldFromHost(host){
  return host.split('.').pop();
};

module.exports = tld;
<|endoftext|>


<commit_before>
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const Api = require('./api')

admin.initializeApp(functions.config().firebase)

exports.api = functions.https.onRequest((new Api(admin)).handler)
exports.createUser = functions.auth.user().onCreate(event => {
  return admin.database()
    .ref('/users')
    .child(event.data.uid)
    .set({
      email: event.data.email,
      displayName: event.data.displayName,
      photoURL: event.data.photoURL
      // rating: 750,
      // rated: false,
      // active: true
    })
})
exports.draftMatch = functions.database.ref('/orgs/{org}/users/{userUid}/draft-matches/{matchUid}')
  .onCreate(event => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)
    let playersPromise = orgRef.ref(`/players`).once('value')
    let matchesPromise = orgRef.ref(`/users/${event.params.userUid}/matches/`).once('value')
    let openMatchesPromise = orgRef.ref('/open-match-details').once('value')
    let userOpenMatchesPromise = orgRef.ref(`/users/${event.params.userUid}/open-matches/`).once('value')

    return Promise.all([playersPromise, matchesPromise, openMatchesPromise, userOpenMatchesPromise])
      .then((snaps) => {
        let players = {}
        let playerRating = 0

        if (snaps[3].numChildren() >= 5) {
          return orgRef
            .ref(`/users/${event.params.userUid}/draft-matches/${event.params.matchUid}`)
            .set(null)
        }

        snaps[0].forEach((playerSnapshot) => {
          let player = playerSnapshot.val()
          player.matchesCount = 0
          player.openMatchesCount = 0
          player.uid = playerSnapshot.key
          players[playerSnapshot.key] = player
          if (playerSnapshot.key === event.params.userUid) { playerRating = player.rating }
        })
        snaps[1].forEach((matchSnapshot) => {
          let match = matchSnapshot.val()
          if (match.player1Uid === event.params.userUid) {
            players[match.player2Uid].matchesCount++
          } else {
            players[match.player1Uid].matchesCount++
          }
        })
        snaps[2].forEach((openMatchSnapshot) => {
          let openMatch = openMatchSnapshot.val()
          if (openMatch.player1Uid === event.params.userUid) {
            players[openMatch.player2Uid].hasOpenMatch = true
          } else if (openMatch.player2Uid === event.params.userUid) {
            players[openMatch.player1Uid].hasOpenMatch = true
          }
          players[openMatch.player1Uid].openMatchesCount++
          players[openMatch.player2Uid].openMatchesCount++
        })

        let oponents = Object.keys(players)
          .map(key => players[key])
          .filter((p) => p.active && p.rated && p.uid !== event.params.userUid)
          .filter((p) => !p.hasOpenMatch && p.openMatchesCount < 5)
          .filter((p) => Math.abs(p.rating - playerRating) <= 300)
          .sort((a, b) => a.matchesCount - b.matchesCount ||
            Math.abs(a.rating - playerRating) - Math.abs(b.rating - playerRating))

        if (oponents.length === 0) {
          return orgRef
            .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}/noOponents`)
            .set(true)
        }

        let details = {
          player1Uid: event.params.userUid,
          player1Probability: calcProbability(playerRating, oponents[0].rating),
          player2Uid: oponents[0].uid,
          player2Probability: calcProbability(oponents[0].rating, playerRating),
          timestamp: admin.database.ServerValue.TIMESTAMP
        }

        return orgRef
          .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}`)
          .set(details)
      })
  })
exports.acceptDraftMatch = functions.database.ref('/orgs/{org}/users/{userUid}/draft-matches/{matchUid}/accept')
  .onCreate(event => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    return orgRef
      .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}`)
      .once('value')
      .then((snap) => {
        let draftDetails = snap.val()
        let match = {
          player1Uid: draftDetails.player1Uid,
          player2Uid: draftDetails.player2Uid,
          createdDate: new Date().toISOString()
        }
        let details = Object.assign({
          approvals: {player1: '', player2: ''},
          scores: [{player1: 0, player2: 0}]
        }, match)

        return Promise.all([
          orgRef
            .ref(`/users/${match.player1Uid}/open-matches/${event.params.matchUid}`)
            .set(match),
          orgRef
            .ref(`/users/${match.player2Uid}/open-matches/${event.params.matchUid}`)
            .set(match),
          orgRef
            .ref(`/open-match-details/${event.params.matchUid}`)
            .set(details),
          orgRef
            .ref(`/players/${match.player1Uid}/active`)
            .set(true),
          orgRef
            .ref(`/users/${match.player1Uid}/draft-matches`)
            .set(null),
          orgRef
            .ref(`/users/${match.player1Uid}/draft-match-details`)
            .set(null)
        ])
      })
  })
exports.openMatchDetailsCreated = functions.database.ref('/orgs/{org}/open-match-details/{matchUid}')
  .onCreate(event => {
    let match = event.data.val()
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    return Promise.all([
      orgRef.ref(`/players/${match.player1Uid}`).once('value'),
      orgRef.ref(`/messaging/${match.player2Uid}`).once('value')
    ]).then((snaps) => {
      let player1 = snaps[0].val()
      let player2Messaging = snaps[1].val()
      if (!player2Messaging || !player2Messaging.token) return

      let notificationPayload = {
        notification: {
          title: 'Match Created!',
          body: `${player1.displayName} created ranked match with you. Go and win!`
        }
      }

      return admin.messaging().sendToDevice([player2Messaging.token], notificationPayload)
    })
  })
exports.approvalsChanged = functions.database.ref('/orgs/{org}/open-match-details/{matchUid}/approvals')
  .onUpdate((event) => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    let approvals = event.data.val()
    if (!approvals.player1 || !approvals.player2) { return }

    return orgRef.ref(`/open-match-details/${event.params.matchUid}`)
      .once('value')
      .then((snap) => {
        let details = snap.val()
        let closingApproval = details.scores.reduce((approval, score) => {
          return approval + `|${score.player1}|${score.player2}`
        }, '')
        if (closingApproval !== details.approvals.player1 ||
          closingApproval !== details.approvals.player2) {
          return
        }

        let player1Games = details.scores.filter((s) => Number(s.player1) > Number(s.player2)).length
        let player2Games = details.scores.length - player1Games

        let finalMatch = {
          finalizedDate: new Date().toISOString(),
          createdDate: details.createdDate,
          player1Uid: details.player1Uid,
          player2Uid: details.player2Uid,
          scores: details.scores,
          player1Games: player1Games,
          player2Games: player2Games,
          winnerUid: player1Games > player2Games ? details.player1Uid : details.player2Uid
        }
        return Promise.all([
          orgRef.ref(`/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player1Uid}/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player2Uid}/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player1Uid}/open-matches/${event.params.matchUid}`).set(null),
          orgRef.ref(`/users/${details.player2Uid}/open-matches/${event.params.matchUid}`).set(null),
          orgRef.ref(`/open-match-details/${event.params.matchUid}`).set(null)
        ])
      })
  })
exports.updateRating = functions.database.ref('/orgs/{org}/matches/{matchUid}')
  .onCreate(event => {
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)
    let match = event.data.val()
    return Promise.all([
      orgRef.ref(`/players/${match.player1Uid}`).once('value'),
      orgRef.ref(`/players/${match.player2Uid}`).once('value')
    ]).then((playerRefs) => {
      let p1Rating = playerRefs[0].val().rating
      let p2Rating = playerRefs[1].val().rating
      let p1Score = match.scores.reduce((total, i) => total + Number(i.player1), 0)
      let p2Score = match.scores.reduce((total, i) => total + Number(i.player2), 0)
      let p1Prob = calcProbability(p1Rating, p2Rating)
      let p2Prob = calcProbability(p2Rating, p1Rating)
      let kFactor = 50 * calcKFactorMultiplier(p1Score, p2Score, p1Rating, p2Rating)
      let p1Result = match.player1Games > match.player2Games ? 1 : match.player1Games < match.player2Games ? 0 : 0.5
      let p2Result = 1 - p1Result
      let player1NewRating = calcNewRating(p1Prob, p1Result, p1Rating, kFactor)
      let player2NewRating = calcNewRating(p2Prob, p2Result, p2Rating, kFactor)
      let ratings = {
        player1Prev: p1Rating,
        player2Prev: p2Rating,
        player1New: player1NewRating,
        player2New: player2NewRating
      }

      return Promise.all([
        orgRef.ref(`/players/${match.player1Uid}/rating`)
          .set(player1NewRating),
        orgRef.ref(`/players/${match.player2Uid}/rating`)
          .set(player2NewRating),
        orgRef.ref(`/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/users/${match.player1Uid}/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/users/${match.player2Uid}/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/players/${match.player1Uid}/rated`)
          .set(true),
        orgRef.ref(`/players/${match.player2Uid}/rated`)
          .set(true)
      ])
    })
  })
let calcKFactorMultiplier = (score1, score2, rating1, rating2) => {
  let diff = Math.log(Math.abs(score1 - score2) + 1)
  let ratingDiff = score1 > score2 ? rating1 - rating2 : rating2 - rating1
  return diff * (2.2 / (ratingDiff * 0.001 + 2.2))
}
let calcNewRating = (expected, actual, current, kFactor) => {
  return Math.round(current + kFactor * (actual - expected))
}
let calcProbability = (rating1, rating2) => {
  return 1 / (1 + Math.pow(10, ((rating2 - rating1) / 400)))
}
<commit_msg>added uid to mirrored user<commit_after>
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const Api = require('./api')

admin.initializeApp(functions.config().firebase)

exports.api = functions.https.onRequest((new Api(admin)).handler)
exports.createUser = functions.auth.user().onCreate(event => {
  return admin.database()
    .ref('/users')
    .child(event.data.uid)
    .set({
      uid: event.data.uid,
      email: event.data.email,
      displayName: event.data.displayName,
      photoURL: event.data.photoURL
      // rating: 750,
      // rated: false,
      // active: true
    })
})
exports.draftMatch = functions.database.ref('/orgs/{org}/users/{userUid}/draft-matches/{matchUid}')
  .onCreate(event => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)
    let playersPromise = orgRef.ref(`/players`).once('value')
    let matchesPromise = orgRef.ref(`/users/${event.params.userUid}/matches/`).once('value')
    let openMatchesPromise = orgRef.ref('/open-match-details').once('value')
    let userOpenMatchesPromise = orgRef.ref(`/users/${event.params.userUid}/open-matches/`).once('value')

    return Promise.all([playersPromise, matchesPromise, openMatchesPromise, userOpenMatchesPromise])
      .then((snaps) => {
        let players = {}
        let playerRating = 0

        if (snaps[3].numChildren() >= 5) {
          return orgRef
            .ref(`/users/${event.params.userUid}/draft-matches/${event.params.matchUid}`)
            .set(null)
        }

        snaps[0].forEach((playerSnapshot) => {
          let player = playerSnapshot.val()
          player.matchesCount = 0
          player.openMatchesCount = 0
          player.uid = playerSnapshot.key
          players[playerSnapshot.key] = player
          if (playerSnapshot.key === event.params.userUid) { playerRating = player.rating }
        })
        snaps[1].forEach((matchSnapshot) => {
          let match = matchSnapshot.val()
          if (match.player1Uid === event.params.userUid) {
            players[match.player2Uid].matchesCount++
          } else {
            players[match.player1Uid].matchesCount++
          }
        })
        snaps[2].forEach((openMatchSnapshot) => {
          let openMatch = openMatchSnapshot.val()
          if (openMatch.player1Uid === event.params.userUid) {
            players[openMatch.player2Uid].hasOpenMatch = true
          } else if (openMatch.player2Uid === event.params.userUid) {
            players[openMatch.player1Uid].hasOpenMatch = true
          }
          players[openMatch.player1Uid].openMatchesCount++
          players[openMatch.player2Uid].openMatchesCount++
        })

        let oponents = Object.keys(players)
          .map(key => players[key])
          .filter((p) => p.active && p.rated && p.uid !== event.params.userUid)
          .filter((p) => !p.hasOpenMatch && p.openMatchesCount < 5)
          .filter((p) => Math.abs(p.rating - playerRating) <= 300)
          .sort((a, b) => a.matchesCount - b.matchesCount ||
            Math.abs(a.rating - playerRating) - Math.abs(b.rating - playerRating))

        if (oponents.length === 0) {
          return orgRef
            .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}/noOponents`)
            .set(true)
        }

        let details = {
          player1Uid: event.params.userUid,
          player1Probability: calcProbability(playerRating, oponents[0].rating),
          player2Uid: oponents[0].uid,
          player2Probability: calcProbability(oponents[0].rating, playerRating),
          timestamp: admin.database.ServerValue.TIMESTAMP
        }

        return orgRef
          .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}`)
          .set(details)
      })
  })
exports.acceptDraftMatch = functions.database.ref('/orgs/{org}/users/{userUid}/draft-matches/{matchUid}/accept')
  .onCreate(event => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    return orgRef
      .ref(`/users/${event.params.userUid}/draft-match-details/${event.params.matchUid}`)
      .once('value')
      .then((snap) => {
        let draftDetails = snap.val()
        let match = {
          player1Uid: draftDetails.player1Uid,
          player2Uid: draftDetails.player2Uid,
          createdDate: new Date().toISOString()
        }
        let details = Object.assign({
          approvals: {player1: '', player2: ''},
          scores: [{player1: 0, player2: 0}]
        }, match)

        return Promise.all([
          orgRef
            .ref(`/users/${match.player1Uid}/open-matches/${event.params.matchUid}`)
            .set(match),
          orgRef
            .ref(`/users/${match.player2Uid}/open-matches/${event.params.matchUid}`)
            .set(match),
          orgRef
            .ref(`/open-match-details/${event.params.matchUid}`)
            .set(details),
          orgRef
            .ref(`/players/${match.player1Uid}/active`)
            .set(true),
          orgRef
            .ref(`/users/${match.player1Uid}/draft-matches`)
            .set(null),
          orgRef
            .ref(`/users/${match.player1Uid}/draft-match-details`)
            .set(null)
        ])
      })
  })
exports.openMatchDetailsCreated = functions.database.ref('/orgs/{org}/open-match-details/{matchUid}')
  .onCreate(event => {
    let match = event.data.val()
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    return Promise.all([
      orgRef.ref(`/players/${match.player1Uid}`).once('value'),
      orgRef.ref(`/messaging/${match.player2Uid}`).once('value')
    ]).then((snaps) => {
      let player1 = snaps[0].val()
      let player2Messaging = snaps[1].val()
      if (!player2Messaging || !player2Messaging.token) return

      let notificationPayload = {
        notification: {
          title: 'Match Created!',
          body: `${player1.displayName} created ranked match with you. Go and win!`
        }
      }

      return admin.messaging().sendToDevice([player2Messaging.token], notificationPayload)
    })
  })
exports.approvalsChanged = functions.database.ref('/orgs/{org}/open-match-details/{matchUid}/approvals')
  .onUpdate((event) => {
    if (event.auth.admin) { return }
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)

    let approvals = event.data.val()
    if (!approvals.player1 || !approvals.player2) { return }

    return orgRef.ref(`/open-match-details/${event.params.matchUid}`)
      .once('value')
      .then((snap) => {
        let details = snap.val()
        let closingApproval = details.scores.reduce((approval, score) => {
          return approval + `|${score.player1}|${score.player2}`
        }, '')
        if (closingApproval !== details.approvals.player1 ||
          closingApproval !== details.approvals.player2) {
          return
        }

        let player1Games = details.scores.filter((s) => Number(s.player1) > Number(s.player2)).length
        let player2Games = details.scores.length - player1Games

        let finalMatch = {
          finalizedDate: new Date().toISOString(),
          createdDate: details.createdDate,
          player1Uid: details.player1Uid,
          player2Uid: details.player2Uid,
          scores: details.scores,
          player1Games: player1Games,
          player2Games: player2Games,
          winnerUid: player1Games > player2Games ? details.player1Uid : details.player2Uid
        }
        return Promise.all([
          orgRef.ref(`/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player1Uid}/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player2Uid}/matches/${event.params.matchUid}`).set(finalMatch),
          orgRef.ref(`/users/${details.player1Uid}/open-matches/${event.params.matchUid}`).set(null),
          orgRef.ref(`/users/${details.player2Uid}/open-matches/${event.params.matchUid}`).set(null),
          orgRef.ref(`/open-match-details/${event.params.matchUid}`).set(null)
        ])
      })
  })
exports.updateRating = functions.database.ref('/orgs/{org}/matches/{matchUid}')
  .onCreate(event => {
    let orgRef = admin.database().ref(`/orgs/${event.params.org}`)
    let match = event.data.val()
    return Promise.all([
      orgRef.ref(`/players/${match.player1Uid}`).once('value'),
      orgRef.ref(`/players/${match.player2Uid}`).once('value')
    ]).then((playerRefs) => {
      let p1Rating = playerRefs[0].val().rating
      let p2Rating = playerRefs[1].val().rating
      let p1Score = match.scores.reduce((total, i) => total + Number(i.player1), 0)
      let p2Score = match.scores.reduce((total, i) => total + Number(i.player2), 0)
      let p1Prob = calcProbability(p1Rating, p2Rating)
      let p2Prob = calcProbability(p2Rating, p1Rating)
      let kFactor = 50 * calcKFactorMultiplier(p1Score, p2Score, p1Rating, p2Rating)
      let p1Result = match.player1Games > match.player2Games ? 1 : match.player1Games < match.player2Games ? 0 : 0.5
      let p2Result = 1 - p1Result
      let player1NewRating = calcNewRating(p1Prob, p1Result, p1Rating, kFactor)
      let player2NewRating = calcNewRating(p2Prob, p2Result, p2Rating, kFactor)
      let ratings = {
        player1Prev: p1Rating,
        player2Prev: p2Rating,
        player1New: player1NewRating,
        player2New: player2NewRating
      }

      return Promise.all([
        orgRef.ref(`/players/${match.player1Uid}/rating`)
          .set(player1NewRating),
        orgRef.ref(`/players/${match.player2Uid}/rating`)
          .set(player2NewRating),
        orgRef.ref(`/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/users/${match.player1Uid}/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/users/${match.player2Uid}/matches/${event.params.matchUid}/ratings`)
          .set(ratings),
        orgRef.ref(`/players/${match.player1Uid}/rated`)
          .set(true),
        orgRef.ref(`/players/${match.player2Uid}/rated`)
          .set(true)
      ])
    })
  })
let calcKFactorMultiplier = (score1, score2, rating1, rating2) => {
  let diff = Math.log(Math.abs(score1 - score2) + 1)
  let ratingDiff = score1 > score2 ? rating1 - rating2 : rating2 - rating1
  return diff * (2.2 / (ratingDiff * 0.001 + 2.2))
}
let calcNewRating = (expected, actual, current, kFactor) => {
  return Math.round(current + kFactor * (actual - expected))
}
let calcProbability = (rating1, rating2) => {
  return 1 / (1 + Math.pow(10, ((rating2 - rating1) / 400)))
}
<|endoftext|>


<commit_before>const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//  response.send("Hello from Firebase!");
// });
<commit_msg>Test firebase functions<commit_after>const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.helloWorld = functions.https.onRequest((request, response) => {
    response.send("Hello from Firebase!");
});
<|endoftext|>