<commit_before>// Templating
(function($, global) {
    'use strict';
    var templates = null;

    Handlebars.registerHelper('join', function(context, opts) {
        return context.join(', ');
    });

    Handlebars.registerHelper('date', function(context, opts) {
        return moment(context).format(opts.hash.format || 'YYYY-MM-DD HH:mm:ss');
    });

    function loadTemplates() {
        if(!$.isReady) {
            console.error('Tried loading templates before DOMReady');
            console.trace();
            return false;
        }
        templates = {};
        $('script.template').each(function() {
            var $this = $(this);
            templates[$this.data('id')] = Handlebars.compile($(this).html());
        });
        return true;
    }

    global.renderTemplate = function renderTemplate(name, context) {
        if(templates === null && !loadTemplates()) {
            return 'Cannot use templates before DOMReady. ';
        }
        var tplFunc = templates[name] || function() {
            console.error('Template not found: ' + name);
            console.trace();
            return 'Template "' + name + '" not found. ';
        };
        return $($.parseHTML(tplFunc(context || {})));
    };
})(jQuery, window);

// REST
(function($, global) {
    'use strict';
    global.restCall = function restCall(method, path) {
        return $.ajax({
            url: path,
            type: method
        }).fail(function(xhr, status, error) {
            alert('REST call failed: ' + method + ' ' + path + '\nStatus: ' + status + '\nError: ' + error);
        });
    };
})(jQuery, window);
<commit_msg>Remove template <script> tags after loading them<commit_after>// Templating
(function($, global) {
    'use strict';
    var templates = null;

    Handlebars.registerHelper('join', function(context, opts) {
        return context.join(', ');
    });

    Handlebars.registerHelper('date', function(context, opts) {
        return moment(context).format(opts.hash.format || 'YYYY-MM-DD HH:mm:ss');
    });

    function loadTemplates() {
        if(!$.isReady) {
            console.error('Tried loading templates before DOMReady');
            console.trace();
            return false;
        }
        templates = {};
        $('script.template').each(function() {
            var $this = $(this);
            templates[$this.data('id')] = Handlebars.compile($this.html());
        }).remove();
        return true;
    }

    global.renderTemplate = function renderTemplate(name, context) {
        if(templates === null && !loadTemplates()) {
            return 'Cannot use templates before DOMReady. ';
        }
        var tplFunc = templates[name] || function() {
            console.error('Template not found: ' + name);
            console.trace();
            return 'Template "' + name + '" not found. ';
        };
        return $($.parseHTML(tplFunc(context || {})));
    };
})(jQuery, window);

// REST
(function($, global) {
    'use strict';
    global.restCall = function restCall(method, path) {
        return $.ajax({
            url: path,
            type: method
        }).fail(function(xhr, status, error) {
            alert('REST call failed: ' + method + ' ' + path + '\nStatus: ' + status + '\nError: ' + error);
        });
    };
})(jQuery, window);
<|endoftext|>


<commit_before>var consoleJSON = consoleJSON || {};
var ruleset = ruleset || {};

var DELIMITER = "  ";
var LINE_LENGTH = 80;

consoleJSON.log = function(json, ruleset) {
  // pretty prints JSON to console according to given ruleset
  // obj is a Javascript object, ruleset is a consoleJSON ruleset
  var beginD = consoleJSON.beginDelimiter(json, ruleset);
  if (beginD) {
    consoleJSON.startGroup(beginD, 0, DELIMITER);
  }
  consoleJSON.traverse(json, ruleset, 1);
  var endD = consoleJSON.endDelimiter(json, ruleset);
  if (endD) {
    consoleJSON.print(endD, 0, DELIMITER);
    consoleJSON.endGroup();
  }
  //console.log(json);
};

consoleJSON.traverse = function(json, ruleset, lvl) {
  // traverses the json tree
  // lvl is the depth of the current node (for indentation)
  var type = $.type(json);
  switch (type) {
    case 'array':
      consoleJSON.traverseArray(json, ruleset, lvl);
      break;
    case 'object':
      consoleJSON.traverseObject(json, ruleset, lvl);
      break;
    default:
      var output = consoleJSON.outputPrimitive(json, ruleset);
      consoleJSON.print(output, lvl, DELIMITER);
  }
};

consoleJSON.traverseArray = function(jsonArray, ruleset, lvl) {
  // Traverses an array data type (called from traverse)
  // Handles delimiters and groupings, and other printing rules for arrs
  for (var i = 0; i < jsonArray.length; i++) {
    el = jsonArray[i];
    var type = $.type(el);
    switch (type) {
      case 'array':
      case 'object':
        var beginD = consoleJSON.beginDelimiter(el, ruleset);
        consoleJSON.startGroup(beginD, lvl, DELIMITER);

        consoleJSON.traverse(el, ruleset, lvl+1);

        var endD = consoleJSON.endDelimiter(el, ruleset);
        if (i < jsonArray.length-1) {
          endD = endD + ",";
        }
        consoleJSON.print(endD, lvl, DELIMITER);
        consoleJSON.endGroup();
        break;
      default:
        var output = consoleJSON.outputPrimitive(el, ruleset);
        if (i < jsonArray.length-1) {
          output = output + ",";
        }
        consoleJSON.print(output, lvl, DELIMITER);
    }
  }
};

consoleJSON.traverseObject = function(jsonObj, ruleset, lvl) {
  // Traverses an object data type (called from traverse)
  // Handles delimiters and groupings, and other printing rules for objs
  var ruleset = ruleset || {};
  var keys = Object.keys(jsonObj);
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var keyOutput = consoleJSON.outputKey(key, ruleset);
    var val = jsonObj[key];
    var valType = $.type(val);
    if (!('filter' in ruleset) || ('filter' in ruleset && key == ruleset['filter'])) {
      switch (valType) {
        case 'array':
        case 'object':
          var filterKeyToPutBack = ruleset['filter'];
          delete ruleset['filter']
          var beginD = consoleJSON.beginDelimiter(val, ruleset);
          consoleJSON.startGroup(keyOutput + ": " + beginD, lvl, DELIMITER);

          consoleJSON.traverse(val, ruleset, lvl+1);
          ruleset['filter'] = filterKeyToPutBack;

          var endD = consoleJSON.endDelimiter(val, ruleset);
          if (i < keys.length-1) {
            endD = endD + ",";
          }
          consoleJSON.print(endD, lvl , DELIMITER);
          consoleJSON.endGroup();
          break;
        default:
          var output = consoleJSON.outputVal(val, ruleset);
          if (i < keys.length-1) {
            output = output + ",";
          }
          consoleJSON.print(keyOutput + ": " + output, lvl, DELIMITER);
      }
    } else if (valType == 'array' || valType == 'object') {
      consoleJSON.traverse(val, ruleset, lvl);
    }
  }
};

consoleJSON.beginDelimiter = function(json, ruleset) {
  // Function to handle the opening delimiter for arrays, objs, etc.
  var type = $.type(json);
  switch (type) {
    case 'array':
      return '[';
      break;
    case 'object':
      return '{';
      break;
    default:
      return null;
  }
};

consoleJSON.endDelimiter = function(json, ruleset) {
  // Function to handle the closign delimiter for arrays, objs, etc.
  var type = $.type(json);
  switch (type) {
    case 'array':
      return ']';
      break;
    case 'object':
      return '}';
      break;
    default:
      return null;
  }
};

consoleJSON.outputPrimitive = function(json, ruleset) {
  // Prints a primitive to the output, subject to a ruleset
  var type = $.type(json);
  switch (type) {
    case 'string':
      return '\"' + json + '\"';
      break;
    default:
      return json;
  }
};

consoleJSON.outputKey = function(json, ruleset) {
  // Prints a key to the output, subject to a ruleset
  return consoleJSON.outputPrimitive(json, ruleset);
}

consoleJSON.outputVal = function(json, ruleset) {
  // Prints a value to the output, subjec to a ruleset
  return consoleJSON.outputPrimitive(json, ruleset);
}

// TODO: this also breaks words apart. fix this
consoleJSON.indentWrap = function(target, indentationLvl, delimiter) {
  // A function to handle word wrapping in the console output
  var indent = delimiter.repeat(indentationLvl);
  var remainingLen = LINE_LENGTH - indent.length;
  var result = "";
  var currPos = 0;
  while (currPos+remainingLen < target.length) {
    result += indent + target.substring(currPos,currPos+remainingLen) + "\n";
    currPos += remainingLen;
  }
  result += indent + target.substring(currPos);
  return result;
};

consoleJSON.print = function(target, indentationLvl, delimiter) {
  // Function to write word-wrapped data to console output
  var output = consoleJSON.indentWrap(target, indentationLvl, delimiter);
  console.log(output);
};

consoleJSON.startGroup = function(target, indentationLvl, delimiter) {
  // Begin a console grouping
  var output = consoleJSON.indentWrap(target, indentationLvl, delimiter);
  console.group(output);
};

consoleJSON.endGroup = function() {
  // Finish a console grouping
  console.groupEnd();
};

consoleJSON.filter = function(json, filterKey) {
  // Filter out subtrees of the json
  ruleset['filter'] = filterKey;
  consoleJSON.log(json, ruleset);
  delete ruleset['filter'];
}
// From http://stackoverflow.com/questions/202605/repeat-string-javascript
String.prototype.repeat = function(num) {
  return new Array(num+1).join(this);
};

<commit_msg>Add todo for show hierarchy flag<commit_after>var consoleJSON = consoleJSON || {};
var ruleset = ruleset || {};

var DELIMITER = "  ";
var LINE_LENGTH = 80;

consoleJSON.log = function(json, ruleset) {
  // pretty prints JSON to console according to given ruleset
  // obj is a Javascript object, ruleset is a consoleJSON ruleset
  var beginD = consoleJSON.beginDelimiter(json, ruleset);
  if (beginD) {
    consoleJSON.startGroup(beginD, 0, DELIMITER);
  }
  consoleJSON.traverse(json, ruleset, 1);
  var endD = consoleJSON.endDelimiter(json, ruleset);
  if (endD) {
    consoleJSON.print(endD, 0, DELIMITER);
    consoleJSON.endGroup();
  }
  //console.log(json);
};

consoleJSON.traverse = function(json, ruleset, lvl) {
  // traverses the json tree
  // lvl is the depth of the current node (for indentation)
  var type = $.type(json);
  switch (type) {
    case 'array':
      consoleJSON.traverseArray(json, ruleset, lvl);
      break;
    case 'object':
      consoleJSON.traverseObject(json, ruleset, lvl);
      break;
    default:
      var output = consoleJSON.outputPrimitive(json, ruleset);
      consoleJSON.print(output, lvl, DELIMITER);
  }
};

consoleJSON.traverseArray = function(jsonArray, ruleset, lvl) {
  // Traverses an array data type (called from traverse)
  // Handles delimiters and groupings, and other printing rules for arrs
  for (var i = 0; i < jsonArray.length; i++) {
    el = jsonArray[i];
    var type = $.type(el);
    switch (type) {
      case 'array':
      case 'object':
        var beginD = consoleJSON.beginDelimiter(el, ruleset);
        consoleJSON.startGroup(beginD, lvl, DELIMITER);

        consoleJSON.traverse(el, ruleset, lvl+1);

        var endD = consoleJSON.endDelimiter(el, ruleset);
        if (i < jsonArray.length-1) {
          endD = endD + ",";
        }
        consoleJSON.print(endD, lvl, DELIMITER);
        consoleJSON.endGroup();
        break;
      default:
        var output = consoleJSON.outputPrimitive(el, ruleset);
        if (i < jsonArray.length-1) {
          output = output + ",";
        }
        consoleJSON.print(output, lvl, DELIMITER);
    }
  }
};

consoleJSON.traverseObject = function(jsonObj, ruleset, lvl) {
  // Traverses an object data type (called from traverse)
  // Handles delimiters and groupings, and other printing rules for objs
  var ruleset = ruleset || {};
  var keys = Object.keys(jsonObj);
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var keyOutput = consoleJSON.outputKey(key, ruleset);
    var val = jsonObj[key];
    var valType = $.type(val);
    if (!('filter' in ruleset) || ('filter' in ruleset && key == ruleset['filter'])) {
      switch (valType) {
        case 'array':
        case 'object':
          var filterKeyToPutBack = ruleset['filter'];
          delete ruleset['filter']
          var beginD = consoleJSON.beginDelimiter(val, ruleset);
          consoleJSON.startGroup(keyOutput + ": " + beginD, lvl, DELIMITER);

          consoleJSON.traverse(val, ruleset, lvl+1);
          ruleset['filter'] = filterKeyToPutBack;

          var endD = consoleJSON.endDelimiter(val, ruleset);
          if (i < keys.length-1) {
            endD = endD + ",";
          }
          consoleJSON.print(endD, lvl , DELIMITER);
          consoleJSON.endGroup();
          break;
        default:
          var output = consoleJSON.outputVal(val, ruleset);
          if (i < keys.length-1) {
            output = output + ",";
          }
          consoleJSON.print(keyOutput + ": " + output, lvl, DELIMITER);
      }
    } else if (valType == 'array' || valType == 'object') {
      consoleJSON.traverse(val, ruleset, lvl);
    }
  }
};

consoleJSON.beginDelimiter = function(json, ruleset) {
  // Function to handle the opening delimiter for arrays, objs, etc.
  var type = $.type(json);
  switch (type) {
    case 'array':
      return '[';
      break;
    case 'object':
      return '{';
      break;
    default:
      return null;
  }
};

consoleJSON.endDelimiter = function(json, ruleset) {
  // Function to handle the closign delimiter for arrays, objs, etc.
  var type = $.type(json);
  switch (type) {
    case 'array':
      return ']';
      break;
    case 'object':
      return '}';
      break;
    default:
      return null;
  }
};

consoleJSON.outputPrimitive = function(json, ruleset) {
  // Prints a primitive to the output, subject to a ruleset
  var type = $.type(json);
  switch (type) {
    case 'string':
      return '\"' + json + '\"';
      break;
    default:
      return json;
  }
};

consoleJSON.outputKey = function(json, ruleset) {
  // Prints a key to the output, subject to a ruleset
  return consoleJSON.outputPrimitive(json, ruleset);
}

consoleJSON.outputVal = function(json, ruleset) {
  // Prints a value to the output, subjec to a ruleset
  return consoleJSON.outputPrimitive(json, ruleset);
}

// TODO: this also breaks words apart. fix this
consoleJSON.indentWrap = function(target, indentationLvl, delimiter) {
  // A function to handle word wrapping in the console output
  var indent = delimiter.repeat(indentationLvl);
  var remainingLen = LINE_LENGTH - indent.length;
  var result = "";
  var currPos = 0;
  while (currPos+remainingLen < target.length) {
    result += indent + target.substring(currPos,currPos+remainingLen) + "\n";
    currPos += remainingLen;
  }
  result += indent + target.substring(currPos);
  return result;
};

consoleJSON.print = function(target, indentationLvl, delimiter) {
  // Function to write word-wrapped data to console output
  var output = consoleJSON.indentWrap(target, indentationLvl, delimiter);
  console.log(output);
};

consoleJSON.startGroup = function(target, indentationLvl, delimiter) {
  // Begin a console grouping
  var output = consoleJSON.indentWrap(target, indentationLvl, delimiter);
  console.group(output);
};

consoleJSON.endGroup = function() {
  // Finish a console grouping
  console.groupEnd();
};

// TODO: add show hierarchy flag
consoleJSON.filter = function(json, filterKey) {
  // Filter out subtrees of the json
  ruleset['filter'] = filterKey;
  consoleJSON.log(json, ruleset);
  delete ruleset['filter'];
}
// From http://stackoverflow.com/questions/202605/repeat-string-javascript
String.prototype.repeat = function(num) {
  return new Array(num+1).join(this);
};

<|endoftext|>


<commit_before>import {LightningElement, api, track, wire} from 'lwc';
import {isNotEmpty, debouncify, isUndefined} from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo} from "lightning/uiObjectInfoApi";
import { fireEvent } from 'c/pubsubNoPageRef';
import DI_DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_DONOR_FIELD from '@salesforce/schema/DataImport__c.Donation_Donor__c';
import DONATION_RECORD_TYPE_NAME from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';

const LOOKUP_TYPE = 'REFERENCE';
const PICKLIST_TYPE = 'PICKLIST';
const TEXT_AREA_TYPE = 'TEXTAREA';
const BOOLEAN_TYPE = 'BOOLEAN';
const DELAY = 300;
const RICH_TEXT_FORMATS = [
    'font', 'size', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'clean', 'table', 'header'
];
const CURRENCY = 'currency';
const PERCENT = 'percent';
const DECIMAL = 'decimal';

export default class GeFormField extends LightningElement {
    @track value;
    @track picklistValues = [];
    @track objectDescribeInfo;
    @track richTextValid = true;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _defaultValue = null;
    _recordTypeId;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    handleValueChangeSync = (event) => {
        this.value = this.getValueFromChangeEvent(event);
        const detail = {
            element: this.element,
            value: this.value,
            targetFieldName: this.targetFieldName
        };
        const evt = new CustomEvent('valuechange', {detail, bubbles: true});
        this.dispatchEvent(evt);

        if (this.isLookup) {
            const objMappingDevName =
                GeFormService.importedRecordFieldNames.includes(this.fieldApiName) ?
                    GeFormService.getObjectMappingWrapperByImportedFieldName(this.fieldApiName)
                        .DeveloperName :
                    this.objectMappingDevName;

            const detail = {
                ...event.detail,
                objectMappingDevName: objMappingDevName
            }

            const selectRecordEvent = new CustomEvent(
                'lookuprecordselect',
                { detail: detail });
            this.dispatchEvent(selectRecordEvent);
        }

        if (this.isPicklist) {
            this.handlePicklistChange();
        }

        if(this.isRichText) {
            this.checkRichTextValidity();
        }

        if(this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
            // fire event for reactive widget component containing the Data Import field API name and Value
            // currently only used for the Donation Amount.
            fireEvent(null, 'widgetData', { donationAmount: this.value });
        }
    };

    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    handlePicklistChange() {
        if (this.fieldApiName === DONATION_DONOR_FIELD.fieldApiName) {
            const changeDonationDonorEvent = new CustomEvent(
                'changedonationdonor',
                {detail: {value: this.value}});
            this.dispatchEvent(changeDonationDonorEvent);
        }
    }

    /**
     * Retrieve object metadata. Used to configure how fields are displayed on the form.
     */
    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    wiredObjectInfo(response) {
        if(response.data) {
            this.objectDescribeInfo = response.data;
        }
    }

    connectedCallback() {
        const { defaultValue, recordValue } = this.element;

        if(recordValue) {

            // set the record value to the element value
            this.value = recordValue;
        } else if(defaultValue) {

            // Set the default value if there is one
            // and no record value.
            this._defaultValue = defaultValue;
            this.value = defaultValue;
            this.handlePicklistChange();
        }
    }

    getValueFromChangeEvent(event) {
        if(this.fieldType === BOOLEAN_TYPE) {
            return event.detail.checked.toString();
        } else if(this.isRichText) {
            return event.target.value;
        }

        return event.detail.value;
    }

    /**
     * TRUE when a field is required and filled in correctly, or not required at all.
     * @returns {boolean}
     */
    @api
    isValid() {
        // We need to check for invalid values, regardless if the field is required
        let fieldIsValid = this.checkFieldValidity();

        if(this.element !== null && this.element.required) {
            return isNotEmpty(this.value) && fieldIsValid;
        }

        return fieldIsValid;
    }

    /**
     * TRUE when a field is filled in, and is the correct format.
     * @returns {boolean}
     */
    checkFieldValidity() {
        // TODO: Handle other input types, if needed
        const inputField = this.template.querySelector('[data-id="inputComponent"]');
        if(typeof inputField !== 'undefined'
            && inputField !== null
            && typeof inputField.reportValidity === 'function'
            && typeof inputField.checkValidity === 'function') {
                inputField.reportValidity();
                return inputField.checkValidity();
        } else if(this.isRichText) {
            this.checkRichTextValidity();
            if(!this.richTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this.richTextValid;
        }
        return true;
    }

    checkRichTextValidity() {
        if(this.element.required) {
            const isValid = isNotEmpty(this.value) && this.value.length > 0;
            this.richTextValid = isValid;
            return isValid;
        }
        return true;
    }

    @api
    disable() {
        this._disabled = true;
    }

    @api
    enable() {
        this._disabled = false;
    }

    @api
    get fieldAndValue() {
        let fieldAndValue = {};

        // KIET TBD: This is where we are keeping the field mapping
        // CMT record name at, element.value.
        // However, it may change to the array dataImportFieldMappingDevNames
        // If so, we need to update this to reflect that.
        // In the Execute Anonymous code, both fields are populated.

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            // value is the RecordType Id, but the DataImport's source field expects
            // the RecordType Name
            fieldAndValue[this.formElementName] =
                this.objectDescribeInfo.recordTypeInfos[this.value].name;
        } else {
            fieldAndValue[this.formElementName] = this.value;
        }

        return fieldAndValue;
    }

    get formElementName() {
        return this.element.componentName ? this.element.componentName : this.element.dataImportFieldMappingDevNames[0];
    }

    get inputType() {
        return GeFormService.getInputTypeFromDataType(this.fieldType);
    }

    get formatter() {
        return GeFormService.getNumberFormatterByDescribeType(this.fieldType);
    }

    get required() {
        return (this.fieldInfo && this.fieldInfo.Is_Required && this.fieldType !== BOOLEAN_TYPE) ||
            (this.element && this.element.required);
    }

    get disabled() {
        return this._disabled || (this.element && this.element.disabled);
    }

    get granularity() {
        if (this.formatter) {
            switch (this.fieldType.toLowerCase()) {
                case CURRENCY: return '0.01';
                case PERCENT: return '0.01';
                case DECIMAL: return '0.001';
                default: return 'any';
            }
        }
        return undefined;
    }

    get fieldInfo() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get fieldDescribeInfo() {
        if(this.objectDescribeInfo) {
            return this.objectDescribeInfo.fields[this.fieldApiName];
        }
    }

    get objectInfo() {
        return GeFormService.getObjectMappingWrapper(this.objectMappingDevName);
    }

    get fieldType() {
        return this.fieldInfo.Target_Field_Data_Type;
    }

    get isLightningInput() {
        return typeof GeFormService.getInputTypeFromDataType(this.fieldType) !== 'undefined';
    }

    get isRichText() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE;
    }

    @api
    get isPicklist() {
        return this.fieldType === PICKLIST_TYPE;
    }

    get isTextArea() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return !this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get objectMappingDevName() {
        return this.fieldInfo.Target_Object_Mapping_Dev_Name;
    }

    get objectApiName() {
        if(typeof this.objectInfo !== 'undefined') {
            return this.objectInfo.Object_API_Name;
        }
    }

    get fieldApiName() {
        return this.fieldInfo.Target_Field_API_Name;
    }

    @api
    get sourceFieldAPIName() {
        return this.fieldInfo.Source_Field_API_Name;
    }

    @api
    get fieldLabel() {
        return this.element.customLabel;
    }

    @api
    get fieldValueAndLabel() {

        let fieldWrapper = { value: this.value, label: this.fieldLabel };
        let returnMap = {};
        returnMap[ this.sourceFieldAPIName ] = fieldWrapper;

        return returnMap;

    }

    @api
    setCustomValidity(errorMessage) {

        let inputField = this.template.querySelector('[data-id="inputComponent"]');
        inputField.setCustomValidity(errorMessage);
        inputField.reportValidity();

    }

    @api
    clearCustomValidity() {

        if (this.isLookup) {
            let inputField = this.template.querySelector('[data-id="inputComponent"]');
            inputField.clearCustomValidity();
        } else {
            this.setCustomValidity('');
        }

    }

    /**
     * Load a value into the form field.
     * @param data  An sObject potentially containing a value to load.
     * */
    @api
    load(data) {
        let value;
        if (data.hasOwnProperty(this.sourceFieldAPIName)) {
            value = data[this.sourceFieldAPIName];
            if (value === null || value.value === null) {
                this.reset();
            } else {
                this.value = value.value || value;

                if (this.isLookup) {
                    this.loadLookUp(data, this.value);
                }
            }

            if (this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
                // fire event for reactive widget component containing the Data Import field API name and Value
                // currently only used for the Donation Amount.
                fireEvent(null, 'widgetData', {donationAmount: this.value});
            }

        } else if (!isUndefined(data.value)) {
            // When the geFormField cmp is used inside of geFormWidgetAllocation,
            // geFormWidgetAllocation selects the field cmp to load a value into manually,
            // and passes an {value: <value>} object.  To support that case this block
            // loads the value directly even though data does not have a property for
            // this.sourceFieldAPIName
           this.value = data.value;
        } else {
            // Property isn't defined.  Don't do anything.
            return false;
        }
    }

    /**
     * Loads a value into a look-up field
     * @param data An sObject potentially containing a value to load.
     * @param value A form field value
     */
    loadLookUp(data, value) {
        const lookup = this.template.querySelector('c-ge-form-field-lookup');

        let displayValue;
        const relationshipFieldName = this.sourceFieldAPIName.replace('__c', '__r');

        if (data[relationshipFieldName] &&
            data[relationshipFieldName]['Name']) {
            displayValue = data[relationshipFieldName].Name;

        } else if (data[this.sourceFieldAPIName] &&
            data[this.sourceFieldAPIName]['displayValue']) {
            displayValue = data[this.sourceFieldAPIName].displayValue;

        } else if (data.displayValue) {
            displayValue = data.displayValue;

        }

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            lookup.setSelected({value: value});
        } else {
            lookup.setSelected({value, displayValue});
        }
    }

    @api
    reset() {
        this.value = this._defaultValue;

        if (this.isLookup) {
            const lookup = this.template.querySelector('c-ge-form-field-lookup');
            lookup.reset();
            if (this.fieldApiName === 'RecordTypeId') {
                // Using setTimeout here ensures that this recordTypeId
                // will be set on sibling fields after they are reset by queueing the event.
                setTimeout(() => {
                    this.fireLookupRecordSelectEvent();
                }, 0);
            }
        }

        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist').reset();
            this.handlePicklistChange();
        }
    }

    @api
    set recordTypeId(id) {
        this._recordTypeId = id;
        this.setRecordTypeIdOnChildComponents();
    }

    get recordTypeId() {
        return this._recordTypeId;
    }

    setRecordTypeIdOnChildComponents() {
        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist')
                .recordTypeId = this.recordTypeId;
        }
    }

    renderedCallback() {
        if (this.value && this.fieldApiName === 'RecordTypeId') {
            this.fireLookupRecordSelectEvent();
        }
    }

    fireLookupRecordSelectEvent() {
        this.dispatchEvent(new CustomEvent(
            'lookuprecordselect',
            {
                detail: {
                    value: this.value,
                    displayValue: this.value,
                    fieldApiName: this.fieldApiName,
                    objectMappingDevName: this.objectMappingDevName
                }
            }
        ));
    }

}<commit_msg>Get RecordType Name from stored objectInfo object instead of querying<commit_after>import {LightningElement, api, track, wire} from 'lwc';
import {isNotEmpty, debouncify, isUndefined} from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo} from "lightning/uiObjectInfoApi";
import { fireEvent } from 'c/pubsubNoPageRef';
import DI_DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_DONOR_FIELD from '@salesforce/schema/DataImport__c.Donation_Donor__c';
import DONATION_RECORD_TYPE_NAME from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';

const LOOKUP_TYPE = 'REFERENCE';
const PICKLIST_TYPE = 'PICKLIST';
const TEXT_AREA_TYPE = 'TEXTAREA';
const BOOLEAN_TYPE = 'BOOLEAN';
const DELAY = 300;
const RICH_TEXT_FORMATS = [
    'font', 'size', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'clean', 'table', 'header'
];
const CURRENCY = 'currency';
const PERCENT = 'percent';
const DECIMAL = 'decimal';

export default class GeFormField extends LightningElement {
    @track value;
    @track picklistValues = [];
    @track objectDescribeInfo;
    @track richTextValid = true;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _defaultValue = null;
    _recordTypeId;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    handleValueChangeSync = (event) => {
        this.value = this.getValueFromChangeEvent(event);
        const detail = {
            element: this.element,
            value: this.value,
            targetFieldName: this.targetFieldName
        };
        const evt = new CustomEvent('valuechange', {detail, bubbles: true});
        this.dispatchEvent(evt);

        if (this.isLookup) {
            const objMappingDevName =
                GeFormService.importedRecordFieldNames.includes(this.fieldApiName) ?
                    GeFormService.getObjectMappingWrapperByImportedFieldName(this.fieldApiName)
                        .DeveloperName :
                    this.objectMappingDevName;

            const detail = {
                ...event.detail,
                objectMappingDevName: objMappingDevName
            }

            const selectRecordEvent = new CustomEvent(
                'lookuprecordselect',
                { detail: detail });
            this.dispatchEvent(selectRecordEvent);
        }

        if (this.isPicklist) {
            this.handlePicklistChange();
        }

        if(this.isRichText) {
            this.checkRichTextValidity();
        }

        if(this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
            // fire event for reactive widget component containing the Data Import field API name and Value
            // currently only used for the Donation Amount.
            fireEvent(null, 'widgetData', { donationAmount: this.value });
        }
    };

    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    handlePicklistChange() {
        if (this.fieldApiName === DONATION_DONOR_FIELD.fieldApiName) {
            const changeDonationDonorEvent = new CustomEvent(
                'changedonationdonor',
                {detail: {value: this.value}});
            this.dispatchEvent(changeDonationDonorEvent);
        }
    }

    /**
     * Retrieve object metadata. Used to configure how fields are displayed on the form.
     */
    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    wiredObjectInfo(response) {
        if(response.data) {
            this.objectDescribeInfo = response.data;
        }
    }

    connectedCallback() {
        const { defaultValue, recordValue } = this.element;

        if(recordValue) {

            // set the record value to the element value
            this.value = recordValue;
        } else if(defaultValue) {

            // Set the default value if there is one
            // and no record value.
            this._defaultValue = defaultValue;
            this.value = defaultValue;
            this.handlePicklistChange();
        }
    }

    getValueFromChangeEvent(event) {
        if(this.fieldType === BOOLEAN_TYPE) {
            return event.detail.checked.toString();
        } else if(this.isRichText) {
            return event.target.value;
        }

        return event.detail.value;
    }

    /**
     * TRUE when a field is required and filled in correctly, or not required at all.
     * @returns {boolean}
     */
    @api
    isValid() {
        // We need to check for invalid values, regardless if the field is required
        let fieldIsValid = this.checkFieldValidity();

        if(this.element !== null && this.element.required) {
            return isNotEmpty(this.value) && fieldIsValid;
        }

        return fieldIsValid;
    }

    /**
     * TRUE when a field is filled in, and is the correct format.
     * @returns {boolean}
     */
    checkFieldValidity() {
        // TODO: Handle other input types, if needed
        const inputField = this.template.querySelector('[data-id="inputComponent"]');
        if(typeof inputField !== 'undefined'
            && inputField !== null
            && typeof inputField.reportValidity === 'function'
            && typeof inputField.checkValidity === 'function') {
                inputField.reportValidity();
                return inputField.checkValidity();
        } else if(this.isRichText) {
            this.checkRichTextValidity();
            if(!this.richTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this.richTextValid;
        }
        return true;
    }

    checkRichTextValidity() {
        if(this.element.required) {
            const isValid = isNotEmpty(this.value) && this.value.length > 0;
            this.richTextValid = isValid;
            return isValid;
        }
        return true;
    }

    @api
    disable() {
        this._disabled = true;
    }

    @api
    enable() {
        this._disabled = false;
    }

    @api
    get fieldAndValue() {
        let fieldAndValue = {};

        // KIET TBD: This is where we are keeping the field mapping
        // CMT record name at, element.value.
        // However, it may change to the array dataImportFieldMappingDevNames
        // If so, we need to update this to reflect that.
        // In the Execute Anonymous code, both fields are populated.

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            // value is the RecordType Id, but the DataImport's source field expects
            // the RecordType Name
            fieldAndValue[this.formElementName] =
                this.objectDescribeInfo.recordTypeInfos[this.value].name;
        } else {
            fieldAndValue[this.formElementName] = this.value;
        }

        return fieldAndValue;
    }

    get formElementName() {
        return this.element.componentName ? this.element.componentName : this.element.dataImportFieldMappingDevNames[0];
    }

    get inputType() {
        return GeFormService.getInputTypeFromDataType(this.fieldType);
    }

    get formatter() {
        return GeFormService.getNumberFormatterByDescribeType(this.fieldType);
    }

    get required() {
        return (this.fieldInfo && this.fieldInfo.Is_Required && this.fieldType !== BOOLEAN_TYPE) ||
            (this.element && this.element.required);
    }

    get disabled() {
        return this._disabled || (this.element && this.element.disabled);
    }

    get granularity() {
        if (this.formatter) {
            switch (this.fieldType.toLowerCase()) {
                case CURRENCY: return '0.01';
                case PERCENT: return '0.01';
                case DECIMAL: return '0.001';
                default: return 'any';
            }
        }
        return undefined;
    }

    get fieldInfo() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get fieldDescribeInfo() {
        if(this.objectDescribeInfo) {
            return this.objectDescribeInfo.fields[this.fieldApiName];
        }
    }

    get objectInfo() {
        return GeFormService.getObjectMappingWrapper(this.objectMappingDevName);
    }

    get fieldType() {
        return this.fieldInfo.Target_Field_Data_Type;
    }

    get isLightningInput() {
        return typeof GeFormService.getInputTypeFromDataType(this.fieldType) !== 'undefined';
    }

    get isRichText() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE;
    }

    @api
    get isPicklist() {
        return this.fieldType === PICKLIST_TYPE;
    }

    get isTextArea() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return !this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get objectMappingDevName() {
        return this.fieldInfo.Target_Object_Mapping_Dev_Name;
    }

    get objectApiName() {
        if(typeof this.objectInfo !== 'undefined') {
            return this.objectInfo.Object_API_Name;
        }
    }

    get fieldApiName() {
        return this.fieldInfo.Target_Field_API_Name;
    }

    @api
    get sourceFieldAPIName() {
        return this.fieldInfo.Source_Field_API_Name;
    }

    @api
    get fieldLabel() {
        return this.element.customLabel;
    }

    @api
    get fieldValueAndLabel() {

        let fieldWrapper = { value: this.value, label: this.fieldLabel };
        let returnMap = {};
        returnMap[ this.sourceFieldAPIName ] = fieldWrapper;

        return returnMap;

    }

    @api
    setCustomValidity(errorMessage) {

        let inputField = this.template.querySelector('[data-id="inputComponent"]');
        inputField.setCustomValidity(errorMessage);
        inputField.reportValidity();

    }

    @api
    clearCustomValidity() {

        if (this.isLookup) {
            let inputField = this.template.querySelector('[data-id="inputComponent"]');
            inputField.clearCustomValidity();
        } else {
            this.setCustomValidity('');
        }

    }

    /**
     * Load a value into the form field.
     * @param data  An sObject potentially containing a value to load.
     * */
    @api
    load(data) {
        let value;
        if (data.hasOwnProperty(this.sourceFieldAPIName)) {
            value = data[this.sourceFieldAPIName];
            if (value === null || value.value === null) {
                this.reset();
            } else {
                this.value = value.value || value;

                if (this.isLookup) {
                    if (this.fieldApiName === 'RecordTypeId') {
                        if (value && !value.displayName) {
                            // If the RecordTypeId field for a target record is being
                            // loaded with only the Id (like when a Lookup field is
                            // selected/populated on the form), get the RecordType Name
                            // and pass it with the Id to loadLookup
                            data[this.sourceFieldAPIName] = {
                                value: value,
                                displayValue: this.getRecordTypeNameById(value)
                            };
                        }
                    }
                    this.loadLookUp(data, this.value);
                }
            }

            if (this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
                // fire event for reactive widget component containing the Data Import field API name and Value
                // currently only used for the Donation Amount.
                fireEvent(null, 'widgetData', {donationAmount: this.value});
            }

        } else if (!isUndefined(data.value)) {
            // When the geFormField cmp is used inside of geFormWidgetAllocation,
            // geFormWidgetAllocation selects the field cmp to load a value into manually,
            // and passes an {value: <value>} object.  To support that case this block
            // loads the value directly even though data does not have a property for
            // this.sourceFieldAPIName
           this.value = data.value;
        } else {
            // Property isn't defined.  Don't do anything.
            return false;
        }
    }

    /**
     * Loads a value into a look-up field
     * @param data An sObject potentially containing a value to load.
     * @param value A form field value
     */
    loadLookUp(data, value) {
        const lookup = this.template.querySelector('c-ge-form-field-lookup');

        let displayValue;
        const relationshipFieldName = this.sourceFieldAPIName.replace('__c', '__r');

        if (data[relationshipFieldName] &&
            data[relationshipFieldName]['Name']) {
            displayValue = data[relationshipFieldName].Name;

        } else if (data[this.sourceFieldAPIName] &&
            data[this.sourceFieldAPIName]['displayValue']) {
            displayValue = data[this.sourceFieldAPIName].displayValue;

        } else if (data.displayValue) {
            displayValue = data.displayValue;

        }

        lookup.setSelected({value, displayValue});
    }

    @api
    reset() {
        this.value = this._defaultValue;

        if (this.isLookup) {
            const lookup = this.template.querySelector('c-ge-form-field-lookup');
            lookup.reset();
            if (this.fieldApiName === 'RecordTypeId') {
                // Using setTimeout here ensures that this recordTypeId
                // will be set on sibling fields after they are reset by queueing the event.
                setTimeout(() => {
                    this.fireLookupRecordSelectEvent();
                }, 0);
            }
        }

        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist').reset();
            this.handlePicklistChange();
        }
    }

    @api
    set recordTypeId(id) {
        this._recordTypeId = id;
        this.setRecordTypeIdOnChildComponents();
    }

    get recordTypeId() {
        return this._recordTypeId;
    }

    setRecordTypeIdOnChildComponents() {
        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist')
                .recordTypeId = this.recordTypeId;
        }
    }

    renderedCallback() {
        if (this.value && this.fieldApiName === 'RecordTypeId') {
            this.fireLookupRecordSelectEvent();
        }
    }

    fireLookupRecordSelectEvent() {
        this.dispatchEvent(new CustomEvent(
            'lookuprecordselect',
            {
                detail: {
                    value: this.value,
                    displayValue: this.value,
                    fieldApiName: this.fieldApiName,
                    objectMappingDevName: this.objectMappingDevName
                }
            }
        ));
    }

    /**
     * @description Returns the name of the RecordType that corresponds to a RecordType Id.
     *              This method references this component's objectInfo.recordTypeInfos
     *              property.
     * @param Id The Id of the RecordType.
     * @returns {string|null} The name of the RecordType or null if the RecordType was
     *          not found.
     */
    getRecordTypeNameById(Id) {
        if (this.objectDescribeInfo &&
            this.objectDescribeInfo.recordTypeInfos &&
            this.objectDescribeInfo.recordTypeInfos[Id]) {
            return this.objectDescribeInfo.recordTypeInfos[Id].name;
        } else {
            return null;
        }
    }

}<|endoftext|>


<commit_before>var pancakeSombrero = (function() {

	function splitList(list, target) {
		listLength = list.length;
		firstHalf = [];
		secondHalf = [];
		splitIndex = list.indexOf(target);

		if (splitIndex == -1) {
			return false
		}

		for (var i = 0; i < splitIndex; i++) {
			firstHalf.push(list[i]);
		}

		for (var j = listLength - 1; j > splitIndex; j--) {
			secondHalf.push(list[j]);
		}

		return [firstHalf, secondHalf];
	}

	return {
		splitList: splitList
	}

})();
<commit_msg>throwing exceptions for incorrect user input<commit_after>var pancakeSombrero = (function() {

	function splitList(list, target) {
		listLength = list.length;
		firstHalf = [];
		secondHalf = [];
		splitIndex = list.indexOf(target);

		if (splitIndex == 0 || splitIndex == list.length - 1) {
			throw "You cannot split with a target that equals the first or last element in a list";
		}

		if (splitIndex == -1) {
			throw "Target doesn't exist in list";
		}

		for (var i = 0; i < splitIndex; i++) {
			firstHalf.push(list[i]);
		}

		for (var j = listLength - 1; j > splitIndex; j--) {
			secondHalf.push(list[j]);
		}

		return [firstHalf, secondHalf];
	}

	return {
		splitList: splitList
	}

})();
<|endoftext|>


<commit_before>import {
  REGISTER_START, REGISTER_COMPLETE,
  LOGIN_START, LOGIN_COMPLETE, SET_TOKEN,
  LOGOUT_START, LOGOUT_COMPLETE
} from '../constants/actionTypes/auth';
import { LOAD_ALL_PLAYLISTS_START } from '../constants/actionTypes/playlists';
import { get, post } from '../utils/Request';
import * as Session from '../utils/Session';
import * as Socket from '../utils/Socket';
import { advance, loadHistory } from './BoothActionCreators';
import {
  setPlaylists, selectPlaylist, activatePlaylistComplete
} from './PlaylistActionCreators';
import { closeLoginDialog } from './DialogActionCreators';
import { setUsers } from './UserActionCreators';
import { currentUserSelector, tokenSelector } from '../selectors/userSelectors';

const debug = require('debug')('uwave:actions:login');

export function loginComplete({ jwt, user }) {
  Socket.auth(jwt);
  return dispatch => {
    dispatch({
      type: LOGIN_COMPLETE,
      payload: { jwt, user }
    });
    dispatch(closeLoginDialog());
  };
}

export function loadedState(state) {
  return (dispatch, getState) => {
    dispatch(setUsers(state.users || []));
    dispatch(setPlaylists(state.playlists || []));
    if (state.booth) {
      // TODO don't set this when logging in _after_ entering the page?
      dispatch(advance(state.booth));
    }
    if (state.user) {
      const token = tokenSelector(getState());
      dispatch(loginComplete({
        jwt: token,
        user: state.user
      }));
    }
    if (state.activePlaylist) {
      dispatch(activatePlaylistComplete(state.activePlaylist));
      dispatch(selectPlaylist(state.activePlaylist));
    }
  };
}

export function initState() {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch({ type: LOAD_ALL_PLAYLISTS_START });
    get(jwt, '/v1/now')
      .then(res => res.json())
      .then(state => dispatch(loadedState(state)));

    dispatch(loadHistory());
  };
}

export function setJWT(jwt) {
  return {
    type: SET_TOKEN,
    payload: { jwt }
  };
}

function loginStart() {
  return { type: LOGIN_START };
}

export function login({ email, password }) {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch(loginStart());
    post(jwt, '/v1/auth/login', { email, password })
      .then(res => res.json())
      .then(res => {
        Session.set(res.jwt);
        dispatch(setJWT(res.jwt));
        dispatch(initState());
        return res;
      })
      .catch(error => {
        dispatch({
          type: LOGIN_COMPLETE,
          error: true,
          payload: error
        });
      });
  };
}

export function register({ email, username, password }) {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch({ type: REGISTER_START });
    post(jwt, '/v1/auth/register', { email, username, password, passwordRepeat: password })
      .then(res => res.json())
      .then(user => {
        debug('registered', user);
        dispatch({
          type: REGISTER_COMPLETE,
          payload: { user }
        });
        dispatch(login({ email, password }));
      })
      .catch(err => {
        debug('registration failed', err);
        dispatch({
          type: REGISTER_COMPLETE,
          error: true,
          payload: err
        });
      });
  };
}

function logoutStart() {
  return { type: LOGOUT_START };
}

function logoutComplete() {
  return dispatch => {
    dispatch({ type: LOGOUT_COMPLETE });
    setPlaylists([]);
  };
}

export function logout() {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    const me = currentUserSelector(getState());
    if (me) {
      dispatch(logoutStart());
      del(jwt, `/v1/auth/session/${me._id}`)
        .then(logoutComplete)
        .catch(logoutComplete)
        .then(dispatch);
    } else {
      dispatch(logoutComplete());
    }
    Session.unset();
  };
}
<commit_msg>waitlist: initialise WL state on load<commit_after>import {
  REGISTER_START, REGISTER_COMPLETE,
  LOGIN_START, LOGIN_COMPLETE, SET_TOKEN,
  LOGOUT_START, LOGOUT_COMPLETE
} from '../constants/actionTypes/auth';
import { LOAD_ALL_PLAYLISTS_START } from '../constants/actionTypes/playlists';
import { get, post } from '../utils/Request';
import * as Session from '../utils/Session';
import * as Socket from '../utils/Socket';
import { advance, loadHistory } from './BoothActionCreators';
import {
  setPlaylists, selectPlaylist, activatePlaylistComplete
} from './PlaylistActionCreators';
import { closeLoginDialog } from './DialogActionCreators';
import { setUsers } from './UserActionCreators';
import { setWaitList } from './WaitlistActionCreators';
import { currentUserSelector, tokenSelector } from '../selectors/userSelectors';

const debug = require('debug')('uwave:actions:login');

export function loginComplete({ jwt, user }) {
  Socket.auth(jwt);
  return dispatch => {
    dispatch({
      type: LOGIN_COMPLETE,
      payload: { jwt, user }
    });
    dispatch(closeLoginDialog());
  };
}

export function loadedState(state) {
  return (dispatch, getState) => {
    dispatch(setUsers(state.users || []));
    dispatch(setPlaylists(state.playlists || []));
    dispatch(setWaitList({
      waitlist: state.waitlist,
      locked: state.waitlistLocked
    }));
    if (state.booth) {
      // TODO don't set this when logging in _after_ entering the page?
      dispatch(advance(state.booth));
    }
    if (state.user) {
      const token = tokenSelector(getState());
      dispatch(loginComplete({
        jwt: token,
        user: state.user
      }));
    }
    if (state.activePlaylist) {
      dispatch(activatePlaylistComplete(state.activePlaylist));
      dispatch(selectPlaylist(state.activePlaylist));
    }
  };
}

export function initState() {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch({ type: LOAD_ALL_PLAYLISTS_START });
    get(jwt, '/v1/now')
      .then(res => res.json())
      .then(state => dispatch(loadedState(state)));

    dispatch(loadHistory());
  };
}

export function setJWT(jwt) {
  return {
    type: SET_TOKEN,
    payload: { jwt }
  };
}

function loginStart() {
  return { type: LOGIN_START };
}

export function login({ email, password }) {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch(loginStart());
    post(jwt, '/v1/auth/login', { email, password })
      .then(res => res.json())
      .then(res => {
        Session.set(res.jwt);
        dispatch(setJWT(res.jwt));
        dispatch(initState());
        return res;
      })
      .catch(error => {
        dispatch({
          type: LOGIN_COMPLETE,
          error: true,
          payload: error
        });
      });
  };
}

export function register({ email, username, password }) {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    dispatch({ type: REGISTER_START });
    post(jwt, '/v1/auth/register', { email, username, password, passwordRepeat: password })
      .then(res => res.json())
      .then(user => {
        debug('registered', user);
        dispatch({
          type: REGISTER_COMPLETE,
          payload: { user }
        });
        dispatch(login({ email, password }));
      })
      .catch(err => {
        debug('registration failed', err);
        dispatch({
          type: REGISTER_COMPLETE,
          error: true,
          payload: err
        });
      });
  };
}

function logoutStart() {
  return { type: LOGOUT_START };
}

function logoutComplete() {
  return dispatch => {
    dispatch({ type: LOGOUT_COMPLETE });
    setPlaylists([]);
  };
}

export function logout() {
  return (dispatch, getState) => {
    const jwt = tokenSelector(getState());
    const me = currentUserSelector(getState());
    if (me) {
      dispatch(logoutStart());
      del(jwt, `/v1/auth/session/${me._id}`)
        .then(logoutComplete)
        .catch(logoutComplete)
        .then(dispatch);
    } else {
      dispatch(logoutComplete());
    }
    Session.unset();
  };
}
<|endoftext|>


<commit_before>
var ToolbarPopup = function( properties ) {

  OPromise.call( this );

  this.properties = {};

  // Set provided properties through object prototype setter functions
  this.properties.href = properties.href || "";
  this.properties.width = properties.width;
  this.properties.height = properties.height;

};

ToolbarPopup.prototype = Object.create( OPromise.prototype );

ToolbarPopup.prototype.apply = function() {

  chrome.browserAction.setPopup({ "popup": this.href });

};

// API

ToolbarPopup.prototype.__defineGetter__("href", function() {
  return this.properties.href;
});

ToolbarPopup.prototype.__defineSetter__("href", function( val ) {
  this.properties.href = "" + val;

  Queue.enqueue(this, function(done) {

    chrome.browserAction.setPopup({ "popup": ("" + val) });

    done();

  }.bind(this));
});

ToolbarPopup.prototype.__defineGetter__("width", function() {
  return this.properties.width;
});

ToolbarPopup.prototype.__defineSetter__("width", function( val ) {
  this.properties.width = val;
  // not implemented in chromium
  //
  // TODO pass this message to the popup process itself to resize the popup window
});

ToolbarPopup.prototype.__defineGetter__("height", function() {
  return this.properties.height;
});

ToolbarPopup.prototype.__defineSetter__("height", function( val ) {
  this.properties.height = val;
  // not implemented in chromium
  //
  // TODO pass this message to the popup process itself to resize the popup window
});
<commit_msg>Add external URL loading to popup contexts<commit_after>
var ToolbarPopup = function( properties ) {

  OPromise.call( this );

  this.properties = {};

  // Set provided properties through object prototype setter functions
  this.properties.href = properties.href || "";
  this.properties.width = properties.width || 300;
  this.properties.height = properties.height || 200;

  // internal property
  this.isExternalHref = false;

  this.applyHrefVal = function() {
    // If href points to a http or https resource we need to load it via an iframe
    if(this.isExternalHref === true) {
      return "/oex_shim/popup_resourceloader.html?href=" + global.btoa(this.properties.href) +
                "&w=" + this.properties.width + "&h=" + this.properties.height;
    }

    return this.properties.href;
  };

};

ToolbarPopup.prototype = Object.create( OPromise.prototype );

ToolbarPopup.prototype.apply = function() {

  chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });

};

// API

ToolbarPopup.prototype.__defineGetter__("href", function() {
  return this.properties.href;
});

ToolbarPopup.prototype.__defineSetter__("href", function( val ) {
  val = val + ""; // force to type string

  // Check if we have an external href path
  if(val.match(/^https?:\/\//)) {
    this.isExternalHref = true;
  } else {
    this.isExternalHref = false;
  }

  this.properties.href = val;

  Queue.enqueue(this, function(done) {

    chrome.browserAction.setPopup({ "popup": this.applyHrefVal() });

    done();

  }.bind(this));
});

ToolbarPopup.prototype.__defineGetter__("width", function() {
  return this.properties.width;
});

ToolbarPopup.prototype.__defineSetter__("width", function( val ) {
  val = (val + "").replace(/\D/g, '');

  if(val == '') {
    this.properties.width = 300; // default width
  } else {
    this.properties.width = val;
  }

  // not implemented in chromium
  //
  // TODO pass this message to the popup process itself to resize the popup window
});

ToolbarPopup.prototype.__defineGetter__("height", function() {
  return this.properties.height;
});

ToolbarPopup.prototype.__defineSetter__("height", function( val ) {
  val = (val + "").replace(/\D/g, '');

  if(val == '') {
    this.properties.height = 200; // default height
  } else {
    this.properties.height = val;
  }

  // not implemented in chromium
  //
  // TODO pass this message to the popup process itself to resize the popup window
});
<|endoftext|>


<commit_before>const Route = require('../../structures/Route');
const path = require('path');
const Util = require('../../utils/Util');
const moment = require('moment');
const log = require('../../utils/Log');
const jetpack = require('fs-jetpack');
const Busboy = require('busboy');
const fs = require('fs');
/*
	TODO: Strip exif data if the owner/user configured it as such
	TODO: If source has transparency generate a png thumbnail, otherwise a jpg.
	TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover.
	TODO: If source is a video, generate a thumb of the first frame and save the video length.
*/

class uploadPOST extends Route {
	constructor() {
		super('/upload', 'post', { bypassAuth: true });
	}

	async run(req, res, db) {
		const user = await Util.isAuthorized(req);
		if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' });
		return this.uploadFile(req, res, db, user);
	}

	async processFile(req, res, db, user, file) {
		/*
			Check if the user is trying to upload to an album
		*/
		const albumId = req.body.albumid || req.headers.albumid;
		if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
		if (albumId && user) {
			const album = await db.table('albums').where({ id: albumId, userId: user.id }).first();
			if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
		}

		/*
		if (!albumId) log.info('Incoming file');
		else log.info(`Incoming file for album ${albumId}`);
		*/

		let upload = file.data;
		/*
			If it's a chunked upload but this is not the last part of the chunk, just green light.
			Otherwise, put the file together and process it
		*/
		if (file.body.uuid) {
			if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if
				/*
					We got a chunk that is not the last part, send smoke signal that we received it.
				*/
				return res.json({ message: 'Successfully uploaded chunk' });
			}
			/*
				Seems we finally got the last part of a chunk upload
			*/
			const uploadsDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER);
			const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', file.body.uuid);
			const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' });
			const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')));

			const tempFile = {
				filename: Util.getUniqueFilename(originalname),
				originalname,
				size: file.body.totalfilesize
			};

			for (const chunkFile of chunkFiles) {
				try {
					const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop
					await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop
				} catch (error) {
					log.error(error);
				}
			}

			try {
				await jetpack.removeAsync(chunkedFileDir);
			} catch (error) {
				log.error(error);
			}

			upload = tempFile;
		}

		/*
			First let's get the hash of the file. This will be useful to check if the file
			has already been upload by either the user or an anonymous user.
			In case this is true, instead of uploading it again we retrieve the url
			of the file that is already saved and thus don't store extra copies of the same file.
		*/
		const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop
		const exists = await db.table('files') // eslint-disable-line no-await-in-loop
			.where(function() {
				if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this
				else this.where('userId', user.id); // eslint-disable-line no-invalid-this
			})
			.where({ hash })
			.first();

		if (exists) {
			res.json({
				message: 'Successfully uploaded file BUT IT EXISTED ALREADY',
				name: exists.name,
				size: exists.size,
				url: `${process.env.DOMAIN}/${exists.name}`,
				deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`
			});

			return Util.deleteFile(upload.filename);
		}

		/*
			The file doesn't appear to exist yet for this user, so let's
			store the details on the database.
		*/
		const now = moment.utc().toDate();
		let insertedId = null;
		try {
			/*
				This is so fucking dumb
			*/
			if (process.env.DB_CLIENT === 'sqlite3') {
				insertedId = await db.table('files').insert({
					userId: user ? user.id : null,
					name: upload.filename,
					original: upload.originalname,
					type: upload.mimetype || '',
					size: upload.size,
					hash,
					ip: req.ip,
					createdAt: now,
					editedAt: now
				});
			} else {
				insertedId = await db.table('files').insert({
					userId: user ? user.id : null,
					name: upload.filename,
					original: upload.originalname,
					type: upload.mimetype || '',
					size: upload.size,
					hash,
					ip: req.ip,
					createdAt: now,
					editedAt: now
				}, 'id');
			}
		} catch (error) {
			log.error('There was an error saving the file to the database');
			log.error(error);
			return res.status(500).json({ message: 'There was an error uploading the file.' });
		}

		res.json({
			message: 'Successfully uploaded file',
			name: upload.filename,
			size: upload.size,
			url: `${process.env.DOMAIN}/${upload.filename}`,
			deleteUrl: `${process.env.DOMAIN}/api/file/${insertedId[0]}`
		});

		/*
			If the upload had an album specified we make sure to create the relation
			and update the according timestamps..
		*/
		if (albumId) {
			try {
				await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] });
				await db.table('albums').where('id', albumId).update('editedAt', now);
			} catch (error) {
				log.error('There was an error updating editedAt on an album');
				log.error(error);
			}
		}

		/*
			If exif removal has been force service-wide or requested by the user, remove it
		*/
		if (process.env.STRIP_EXIF == 'true') { // || user.settings.stripExif) {
			// Util.removeExif(upload.filename);
		}

		/*
			Generate those thumbnails
		*/
		return Util.generateThumbnails(upload.filename);
	}

	uploadFile(req, res, db, user) {
		const busboy = new Busboy({
			headers: req.headers,
			limits: {
				fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000),
				files: 1
			}
		});

		const fileToUpload = {
			data: {},
			body: {}
		};

		/*
			Note: For this to work on every case, whoever is uploading a chunk
			should really send the body first and the file last. Otherwise lolisafe
			may not catch the field on time and the chunk may end up being saved
			as a standalone file, completely broken.
		*/
		busboy.on('field', (fieldname, val) => {
			if (/^dz/.test(fieldname)) {
				fileToUpload.body[fieldname.substring(2)] = val;
			} else {
				fileToUpload.body[fieldname] = val;
			}
		});

		/*
			Hey ther's a file! Let's upload it.
		*/
		busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
			let name;
			let saveTo;

			/*
				Let check whether the file is part of a chunk upload or if it's a standalone one.
				If the former, we should store them separately and join all the pieces after we
				receive the last one.
			*/
			const ext = path.extname(filename).toLowerCase();
			if (Util.isExtensionBlocked(ext)) return res.status(400).json({ message: 'This extension is not allowed.' });

			if (fileToUpload.body.uuid) {
				name = `${filename}.${fileToUpload.body.chunkindex}`;
				const chunkDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid);
				jetpack.dir(chunkDir);
				saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid, name);
			} else {
				name = Util.getUniqueFilename(filename);
				if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' });
				saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, name);
			}

			/*
				Let's save some metadata for the db.
			*/
			fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype };
			const stream = fs.createWriteStream(saveTo);

			file.on('data', data => {
				fileToUpload.data.size = data.length;
			});

			/*
				The file that is being uploaded is bigger than the limit specified on the config file
				and thus we should close the stream and delete the file.
			*/
			file.on('limit', () => {
				file.unpipe(stream);
				stream.end();
				jetpack.removeAsync(saveTo);
				return res.status(400).json({ message: 'The file is too big.' });
			});

			file.on('error', err => {
				log.error('There was an error uploading a file');
				log.error(err);
				return res.status(500).json({ message: 'There was an error uploading the file.' });
			});

			/*
				TODO: Does this even work??
			*/
			return file.pipe(stream);
		});

		busboy.on('error', err => {
			log.error('There was an error uploading a file');
			log.error(err);
			return res.status(500).json({ message: 'There was an error uploading the file.' });
		});

		busboy.on('finish', () => this.processFile(req, res, db, user, fileToUpload));
		req.pipe(busboy);
	}
}

module.exports = uploadPOST;
<commit_msg>Add TODO<commit_after>const Route = require('../../structures/Route');
const path = require('path');
const Util = require('../../utils/Util');
const moment = require('moment');
const log = require('../../utils/Log');
const jetpack = require('fs-jetpack');
const Busboy = require('busboy');
const fs = require('fs');
/*
	TODO: Strip exif data if the owner/user configured it as such
	TODO: If source has transparency generate a png thumbnail, otherwise a jpg.
	TODO: If source is a gif, generate a thumb of the first frame and play the gif on hover.
	TODO: If source is a video, generate a thumb of the first frame and save the video length.

	TODO: Think if its worth making a folder with the user uuid in uploads/ and upload the pictures there so that this way at least not every single file will be in 1 directory
*/

class uploadPOST extends Route {
	constructor() {
		super('/upload', 'post', { bypassAuth: true });
	}

	async run(req, res, db) {
		const user = await Util.isAuthorized(req);
		if (!user && process.env.PUBLIC_MODE == 'false') return res.status(401).json({ message: 'Not authorized to use this resource' });
		return this.uploadFile(req, res, db, user);
	}

	async processFile(req, res, db, user, file) {
		/*
			Check if the user is trying to upload to an album
		*/
		const albumId = req.body.albumid || req.headers.albumid;
		if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
		if (albumId && user) {
			const album = await db.table('albums').where({ id: albumId, userId: user.id }).first();
			if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
		}

		/*
		if (!albumId) log.info('Incoming file');
		else log.info(`Incoming file for album ${albumId}`);
		*/

		let upload = file.data;
		/*
			If it's a chunked upload but this is not the last part of the chunk, just green light.
			Otherwise, put the file together and process it
		*/
		if (file.body.uuid) {
			if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if
				/*
					We got a chunk that is not the last part, send smoke signal that we received it.
				*/
				return res.json({ message: 'Successfully uploaded chunk' });
			}
			/*
				Seems we finally got the last part of a chunk upload
			*/
			const uploadsDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER);
			const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', file.body.uuid);
			const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' });
			const originalname = Util.getFilenameFromPath(chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.')));

			const tempFile = {
				filename: Util.getUniqueFilename(originalname),
				originalname,
				size: file.body.totalfilesize
			};

			for (const chunkFile of chunkFiles) {
				try {
					const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop
					await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop
				} catch (error) {
					log.error(error);
				}
			}

			try {
				await jetpack.removeAsync(chunkedFileDir);
			} catch (error) {
				log.error(error);
			}

			upload = tempFile;
		}

		/*
			First let's get the hash of the file. This will be useful to check if the file
			has already been upload by either the user or an anonymous user.
			In case this is true, instead of uploading it again we retrieve the url
			of the file that is already saved and thus don't store extra copies of the same file.
		*/
		const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop
		const exists = await db.table('files') // eslint-disable-line no-await-in-loop
			.where(function() {
				if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this
				else this.where('userId', user.id); // eslint-disable-line no-invalid-this
			})
			.where({ hash })
			.first();

		if (exists) {
			res.json({
				message: 'Successfully uploaded file BUT IT EXISTED ALREADY',
				name: exists.name,
				size: exists.size,
				url: `${process.env.DOMAIN}/${exists.name}`,
				deleteUrl: `${process.env.DOMAIN}/api/file/${exists.id}`
			});

			return Util.deleteFile(upload.filename);
		}

		/*
			The file doesn't appear to exist yet for this user, so let's
			store the details on the database.
		*/
		const now = moment.utc().toDate();
		let insertedId = null;
		try {
			/*
				This is so fucking dumb
			*/
			if (process.env.DB_CLIENT === 'sqlite3') {
				insertedId = await db.table('files').insert({
					userId: user ? user.id : null,
					name: upload.filename,
					original: upload.originalname,
					type: upload.mimetype || '',
					size: upload.size,
					hash,
					ip: req.ip,
					createdAt: now,
					editedAt: now
				});
			} else {
				insertedId = await db.table('files').insert({
					userId: user ? user.id : null,
					name: upload.filename,
					original: upload.originalname,
					type: upload.mimetype || '',
					size: upload.size,
					hash,
					ip: req.ip,
					createdAt: now,
					editedAt: now
				}, 'id');
			}
		} catch (error) {
			log.error('There was an error saving the file to the database');
			log.error(error);
			return res.status(500).json({ message: 'There was an error uploading the file.' });
		}

		res.json({
			message: 'Successfully uploaded file',
			name: upload.filename,
			size: upload.size,
			url: `${process.env.DOMAIN}/${upload.filename}`,
			deleteUrl: `${process.env.DOMAIN}/api/file/${insertedId[0]}`
		});

		/*
			If the upload had an album specified we make sure to create the relation
			and update the according timestamps..
		*/
		if (albumId) {
			try {
				await db.table('albumsFiles').insert({ albumId, fileId: insertedId[0] });
				await db.table('albums').where('id', albumId).update('editedAt', now);
			} catch (error) {
				log.error('There was an error updating editedAt on an album');
				log.error(error);
			}
		}

		/*
			If exif removal has been force service-wide or requested by the user, remove it
		*/
		if (process.env.STRIP_EXIF == 'true') { // || user.settings.stripExif) {
			// Util.removeExif(upload.filename);
		}

		/*
			Generate those thumbnails
		*/
		return Util.generateThumbnails(upload.filename);
	}

	uploadFile(req, res, db, user) {
		const busboy = new Busboy({
			headers: req.headers,
			limits: {
				fileSize: parseInt(process.env.MAX_SIZE, 10) * (1000 * 1000),
				files: 1
			}
		});

		const fileToUpload = {
			data: {},
			body: {}
		};

		/*
			Note: For this to work on every case, whoever is uploading a chunk
			should really send the body first and the file last. Otherwise lolisafe
			may not catch the field on time and the chunk may end up being saved
			as a standalone file, completely broken.
		*/
		busboy.on('field', (fieldname, val) => {
			if (/^dz/.test(fieldname)) {
				fileToUpload.body[fieldname.substring(2)] = val;
			} else {
				fileToUpload.body[fieldname] = val;
			}
		});

		/*
			Hey ther's a file! Let's upload it.
		*/
		busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
			let name;
			let saveTo;

			/*
				Let check whether the file is part of a chunk upload or if it's a standalone one.
				If the former, we should store them separately and join all the pieces after we
				receive the last one.
			*/
			const ext = path.extname(filename).toLowerCase();
			if (Util.isExtensionBlocked(ext)) return res.status(400).json({ message: 'This extension is not allowed.' });

			if (fileToUpload.body.uuid) {
				name = `${filename}.${fileToUpload.body.chunkindex}`;
				const chunkDir = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid);
				jetpack.dir(chunkDir);
				saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, 'chunks', fileToUpload.body.uuid, name);
			} else {
				name = Util.getUniqueFilename(filename);
				if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' });
				saveTo = path.join(__dirname, '..', '..', '..', '..', process.env.UPLOAD_FOLDER, name);
			}

			/*
				Let's save some metadata for the db.
			*/
			fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype };
			const stream = fs.createWriteStream(saveTo);

			file.on('data', data => {
				fileToUpload.data.size = data.length;
			});

			/*
				The file that is being uploaded is bigger than the limit specified on the config file
				and thus we should close the stream and delete the file.
			*/
			file.on('limit', () => {
				file.unpipe(stream);
				stream.end();
				jetpack.removeAsync(saveTo);
				return res.status(400).json({ message: 'The file is too big.' });
			});

			file.on('error', err => {
				log.error('There was an error uploading a file');
				log.error(err);
				return res.status(500).json({ message: 'There was an error uploading the file.' });
			});

			/*
				TODO: Does this even work??
			*/
			return file.pipe(stream);
		});

		busboy.on('error', err => {
			log.error('There was an error uploading a file');
			log.error(err);
			return res.status(500).json({ message: 'There was an error uploading the file.' });
		});

		busboy.on('finish', () => this.processFile(req, res, db, user, fileToUpload));
		req.pipe(busboy);
	}
}

module.exports = uploadPOST;
<|endoftext|>