<commit_before>import {AssignmentReactiveVars} from "./AssignmentReactiveVars"
import {TeamService} from "../../../both/service/TeamService"

class AssignmentTasksList extends BlazeComponent {
    constructor(parent) {
        super();
        this.taskTeamFilter = new ReactiveVar(AssignmentReactiveVars.defaultFilter);
        this.isplayAssignedTask = new ReactiveVar(false);
        this.taskSkillsFilter = new ReactiveVar(null);
        this.taskNeededTeamFilter = new ReactiveVar(null); //ok

    }

    events() {
        return [{
            "click .href-assignment-task": this.onClickTaskName,
            "click .task": this.onClickTask,
            "click li.peopleNeed": this.onClickPeopleNeed,
            "click .tasks-list-header": this.switchTasksListDeveloped,
            "keyup #search_task_name": this.performSearch,
            "change #display-assigned-task-checkbox": this.switchDisplayAssignedTask
        }]
    }

    onClickTaskName(event) {
        console.info("routing", "/assignment/task/" + this.currentData()._id);
        Router.go("/assignment/task/" + this.currentData()._id);
    }

    onClickTask(event) {
        AssignmentReactiveVars.SelectedTaskBreadCrumb.set(this.currentData());
    }

    onClickPeopleNeed(event) {
        event.stopPropagation();
        var currentAssignmentType = AssignmentReactiveVars.CurrentAssignmentType.get();
        var isUnassignment = AssignmentReactiveVars.IsUnassignment.get();
        var target = $(event.target);
        var _idTask, _idTimeSlot;
        if (target.hasClass("task"))
            _idTask = target.data("_id");
        else
            _idTask = target.parents(".task").data("_id");

        AssignmentReactiveVars.SelectedPeopleNeed.set(this.currentData());

        var userId = AssignmentReactiveVars.SelectedUser.get()._id;

        if (target.hasClass("time-slot"))
            _idTimeSlot = target.data("_id");
        else
            _idTimeSlot = target.parents(".time-slot").data("_id");

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                if (isUnassignment) {
                    Meteor.call("removeAssignUserToTaskTimeSlot", AssignmentReactiveVars.SelectedPeopleNeed.get()._id, userId);
                    AssignmentReactiveVars.IsUnassignment.set(false);
                } else
                    Meteor.call("assignUserToTaskTimeSlot", AssignmentReactiveVars.SelectedPeopleNeed.get()._id, userId, function (error, result) {
                        if (!error) {
                            //we have to reset the task list this way because once assigned, the task can't propose any peopleNeed for the user as he is
                            //no longer available but the task can still be theoretically assigned to him because we didn't recompute the AssignmentReactiveVars.TaskFilter job done
                            //by the event on the calendar.
                            //we don't need to do that for the mode TASKTOUSER as the user naturally disappears from the list once assigned
                            AssignmentReactiveVars.TaskFilter.set(AssignmentReactiveVars.noneFilter);

                            //reset workflow
                            AssignmentReactiveVars.SelectedAvailability.set(null);
                        }
                    });
                break;
            case AssignmentType.TASKTOUSER:
                break;
        }

    }

    switchTasksListDeveloped(event){
        AssignmentReactiveVars.isTasksListDeveloped.set(!AssignmentReactiveVars.isTasksListDeveloped.get());
        if(AssignmentReactiveVars.isUsersListDeveloped.get()){
            AssignmentReactiveVars.isUsersListDeveloped.set(false);
        }
    }

    isTasksListDeveloped(){ return AssignmentReactiveVars.isTasksListDeveloped.get()}

    performSearch(event) {
        var searchInput = $("#search_task_name").val();

        //desactivation de la recherche par URL
        //console.info("routing", "/assignment/task/search/"+query);
        //Router.go("/assignment/task/search/"+query);

        if (searchInput === "") {
            AssignmentReactiveVars.TaskIndexFilter.set(AssignmentReactiveVars.noSearchFilter);
        } else {
            AssignmentReactiveVars.TaskIndexFilter.set(searchInput);
        }
    }

    filterRespTeam(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _id = newOption;
            if (!_id) {
                this.taskTeamFilter.set(AssignmentReactiveVars.defaultFilter);
            } else {
                this.taskTeamFilter.set({
                    teamId: _id
                });
            }
        },this);
    }

    filterNeededTeam(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _id = newOption;
            if (!_id) {
                this.taskNeededTeamFilter.set(null);
            //TODO add this "noNeededTeam" in the select
            } else if (_id === "noNeededTeam") {
                this.taskNeededTeamFilter.set("noNeededTeam");
            }
            else {
                this.taskNeededTeamFilter.set(_id);
            }
        },this);
    }

    filterSkill(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _ids = newOption;
            if (!_ids || _ids=="") {
                this.taskSkillsFilter.set(null);
            //TODO add this "noSkills" option in the select
            } else if (_ids[0] === "noSkills") {
                this.taskSkillsFilter.set([]);
            } else {
                this.taskSkillsFilter.set(_ids);
            }
        },this);
    }

    switchDisplayAssignedTask(event) {
        this.isplayAssignedTask.set($($(event.target)[0]).is(':checked'));
    }

    optionQueryTeamsWithoutAlreadyAssigned(){
        return TeamService.optionQueryTeamsWithoutAlreadyAssigned();
    }

    tasks() {
        var filter = AssignmentReactiveVars.TaskFilter.get();
        var filterIndex = AssignmentReactiveVars.TaskIndexFilter.get();
        var teamFilter = this.taskTeamFilter.get();
        var displayAssignedTask = this.isplayAssignedTask.get();
        var assignmentType = AssignmentReactiveVars.CurrentAssignmentType.get();

        var skillsFilter = this.taskSkillsFilter.get();
        var neededTeamFilter = this.taskNeededTeamFilter.get();
        var skillsAndNeededTeamFilterForAssigned = {};

        if (displayAssignedTask && assignmentType === AssignmentType.TASKTOUSER) {
            skillsAndNeededTeamFilterForAssigned = {
                timeSlots: {
                    $elemMatch: {
                        peopleNeededAssigned: {
                            $elemMatch: {
                                //below attributes will be added just after as it
                                //skills: skillsFilter,
                                //teamId: neededTeamFilter
                            }
                        }
                    }
                }
            };
            if (skillsFilter)
                skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.skills = {$all: skillsFilter};
            if (neededTeamFilter) {
                if (neededTeamFilter === "noNeededTeam")
                    skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.teamId = null;
                else
                    skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.teamId = neededTeamFilter;
            }
        }
        var skillsAndNeededTeamFilter = {
            timeSlots: {
                $elemMatch: {
                    peopleNeeded: {
                        $elemMatch: {
                            //below attributes will be added just after as it
                            //skills: skillsFilter,
                            //teamId: neededTeamFilter
                        }
                    }
                }
            }
        };
        if (skillsFilter)
            skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.skills = {$all: skillsFilter};
        if (neededTeamFilter) {
            if (neededTeamFilter === "noNeededTeam")
                skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.teamId = null;
            else
                skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.teamId = neededTeamFilter;
        }

        var searchResult;
        var filterResult;

        if (displayAssignedTask && assignmentType === AssignmentType.TASKTOUSER) {
            filterResult = Tasks.find({
                $and: [
                    filter,
                    teamFilter,
                    {
                        $or: [
                            skillsAndNeededTeamFilter,
                            skillsAndNeededTeamFilterForAssigned
                        ]
                    }
                ]
            }, {limit: 20}).fetch();
        } else {
            filterResult = Tasks.find({
                $and: [
                    filter,
                    teamFilter,
                    skillsAndNeededTeamFilter,
                ]
            }, {limit: 20}).fetch();
        }

        searchResult = TasksIndex.search(filterIndex, {limit: 20}).fetch();
        return _.intersectionObjects(searchResult, filterResult);
    }

    team() {
        return Teams.findOne({_id: this.currentData().teamId}).name;
    }

    user() {
        return Users.findOne({_id: this.currentData().userId}).name;
    }

    timeSlotsInfo() {
        var task = this.currentData();
        var timeSlots = task.timeSlots;
        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];
            _.each(timeSlots, (timeSlot) => {
                var date = AssignmentReactiveVars.SelectedDate.get();
                var start = new moment(timeSlot.start);
                var end = new moment(timeSlot.end);
                if ((start.isBefore(date) || start.isSame(date)) &&
                    (end.isAfter(date) || end.isSame(date))) {
                    result.push(timeSlot);
                }
            });

            var string = " - "
            _.each(result, function (timeSlot) {
                string += new moment(timeSlot.start).format("H[h]mm") + " - " + new moment(timeSlot.end).format("H[h]mm");
            });
            return string;
        } else {
            return; //timeSlot infos can be directly see in the calendar
        }
    }

    timeSlots() {
        var task = this.currentData();
        var timeSlots = task.timeSlots;
        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];
            _.each(timeSlots, (timeSlot) => {
                var date = AssignmentReactiveVars.SelectedDate.get();
                var start = new moment(timeSlot.start);
                var end = new moment(timeSlot.end);
                if ((start.isBefore(date) || start.isSame(date)) &&
                    (end.isAfter(date) || end.isSame(date))) {
                    result.push(timeSlot);
                }
            });
            return result;
        } else {
            return []; //timeSlot infos can be directly see in the calendar
        }
    }

    peopleNeeded() {
        var peopleNeeded = this.currentData().peopleNeeded;

        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];

            _.each(peopleNeeded, (peopleNeed) => {
                var selectedUser = Users.findOne(AssignmentReactiveVars.SelectedUser.get());

                //userId : if existing, selected user must be the one
                if (peopleNeed.userId) {
                    if (peopleNeed.userId === selectedUser._id) {
                        result.push(peopleNeed);
                        return;
                    }
                    return;
                }

                //teamId : if existing, selected user must at least have the required team
                if (peopleNeed.teamId) {
                    if (!_.contains(selectedUser.teams, peopleNeed.teamId)) {
                        return;
                    }
                }

                //if no skills required, we don't care about the user's skills
                if (peopleNeed.skills.length === 0) {
                    result.push(peopleNeed);
                    return;
                }

                //skills : if not empty, user must have all the required skill
                var userHaveAllRequiredSkills = true;
                _.each(peopleNeed.skills, (skill) => {
                    if (!_.contains(selectedUser.skills, skill)) {
                        userHaveAllRequiredSkills = false;
                    }
                });
                if (userHaveAllRequiredSkills) {
                    result.push(peopleNeed);
                    return;
                }
            });

            return result;

        } else {
            return peopleNeeded;
        }

        var skills = timeSlot.skills;
    }

    neededTeamOptions() {
        return {
            chooseLabel: "Choose a needed team",
        };
    }

    responsibleTeamOptions() {
        return {
            chooseLabel: "Choose a responsible team",
        };
    }

}

AssignmentTasksList.register("AssignmentTasksList");

<commit_msg>no message<commit_after>import {AssignmentReactiveVars} from "./AssignmentReactiveVars"
import {TeamService} from "../../../both/service/TeamService"

class AssignmentTasksList extends BlazeComponent {
    constructor(parent) {
        super();
        this.taskTeamFilter = new ReactiveVar(AssignmentReactiveVars.defaultFilter);
        this.isplayAssignedTask = new ReactiveVar(false);
        this.taskSkillsFilter = new ReactiveVar(null);
        this.taskNeededTeamFilter = new ReactiveVar(null); //ok

    }

    events() {
        return [{
            "click .href-assignment-task": this.onClickTaskName,
            "click .task": this.onClickTask,
            "click li.peopleNeed": this.onClickPeopleNeed,
            "click .tasks-list-header": this.switchTasksListDeveloped,
            "keyup #search_task_name": this.performSearch,
            "change #display-assigned-task-checkbox": this.switchDisplayAssignedTask
        }]
    }

    onClickTaskName(event) {
        console.info("routing", "/assignment/task/" + this.currentData()._id);
        Router.go("/assignment/task/" + this.currentData()._id);
    }

    onClickTask(event) {
        AssignmentReactiveVars.SelectedTaskBreadCrumb.set(this.currentData());
    }

    onClickPeopleNeed(event) {
        event.stopPropagation();
        var currentAssignmentType = AssignmentReactiveVars.CurrentAssignmentType.get();
        var isUnassignment = AssignmentReactiveVars.IsUnassignment.get();
        var target = $(event.target);
        var _idTask, _idTimeSlot;
        if (target.hasClass("task"))
            _idTask = target.data("_id");
        else
            _idTask = target.parents(".task").data("_id");

        AssignmentReactiveVars.SelectedPeopleNeed.set(this.currentData());

        var userId = AssignmentReactiveVars.SelectedUser.get()._id;

        if (target.hasClass("time-slot"))
            _idTimeSlot = target.data("_id");
        else
            _idTimeSlot = target.parents(".time-slot").data("_id");

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                if (isUnassignment) {
                    Meteor.call("removeAssignUserToTaskTimeSlot", AssignmentReactiveVars.SelectedPeopleNeed.get()._id, userId);
                    AssignmentReactiveVars.IsUnassignment.set(false);
                } else
                    Meteor.call("assignUserToTaskTimeSlot", AssignmentReactiveVars.SelectedPeopleNeed.get()._id, userId, function (error, result) {
                        if (!error) {
                            //we have to reset the task list this way because once assigned, the task can't propose any peopleNeed for the user as he is
                            //no longer available but the task can still be theoretically assigned to him because we didn't recompute the AssignmentReactiveVars.TaskFilter job done
                            //by the event on the calendar.
                            //we don't need to do that for the mode TASKTOUSER as the user naturally disappears from the list once assigned
                            AssignmentReactiveVars.TaskFilter.set(AssignmentReactiveVars.noneFilter);

                            //reset workflow
                            AssignmentReactiveVars.SelectedAvailability.set(null);
                        }
                    });
                break;
            case AssignmentType.TASKTOUSER:
                break;
        }

    }

    switchTasksListDeveloped(event){
        AssignmentReactiveVars.isTasksListDeveloped.set(!AssignmentReactiveVars.isTasksListDeveloped.get());
        if(AssignmentReactiveVars.isUsersListDeveloped.get()){
            AssignmentReactiveVars.isUsersListDeveloped.set(false);
        }
    }

    isTasksListDeveloped(){ return AssignmentReactiveVars.isTasksListDeveloped.get()}

    performSearch(event) {
        var searchInput = $("#search_task_name").val();

        //desactivation de la recherche par URL
        //console.info("routing", "/assignment/task/search/"+query);
        //Router.go("/assignment/task/search/"+query);

        if (searchInput === "") {
            AssignmentReactiveVars.TaskIndexFilter.set(AssignmentReactiveVars.noSearchFilter);
        } else {
            AssignmentReactiveVars.TaskIndexFilter.set(searchInput);
        }
    }

    filterRespTeam(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _id = newOption;
            if (!_id) {
                this.taskTeamFilter.set(AssignmentReactiveVars.defaultFilter);
            } else {
                this.taskTeamFilter.set({
                    teamId: _id
                });
            }
        },this);
    }

    filterNeededTeam(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _id = newOption;
            if (!_id) {
                this.taskNeededTeamFilter.set(null);
            //TODO add this "noNeededTeam" in the select
            } else if (_id === "noNeededTeam") {
                this.taskNeededTeamFilter.set("noNeededTeam");
            }
            else {
                this.taskNeededTeamFilter.set(_id);
            }
        },this);
    }

    filterSkill(error, docModified, newOption) {
        return _.bind(function (error, docModifier, newOption) {
            var _ids = newOption;
            if (!_ids || _ids=="") {
                this.taskSkillsFilter.set(null);
            //TODO add this "noSkills" option in the select
            } else if (_ids[0] === "noSkills") {
                this.taskSkillsFilter.set([]);
            } else {
                this.taskSkillsFilter.set(_ids);
            }
        },this);
    }

    switchDisplayAssignedTask(event) {
        this.isplayAssignedTask.set($($(event.target)[0]).is(':checked'));
    }

    optionQueryTeamsWithoutAlreadyAssigned(){
        return TeamService.optionQueryTeamsWithoutAlreadyAssigned();
    }

    tasks() {
        var filter = AssignmentReactiveVars.TaskFilter.get();
        var filterIndex = AssignmentReactiveVars.TaskIndexFilter.get();
        var teamFilter = this.taskTeamFilter.get();
        var displayAssignedTask = this.isplayAssignedTask.get();
        var assignmentType = AssignmentReactiveVars.CurrentAssignmentType.get();

        var skillsFilter = this.taskSkillsFilter.get();
        var neededTeamFilter = this.taskNeededTeamFilter.get();
        var skillsAndNeededTeamFilterForAssigned = {};

        //TODO possible de factoriser ca
        if (displayAssignedTask && assignmentType === AssignmentType.TASKTOUSER) {
            skillsAndNeededTeamFilterForAssigned = {
                timeSlots: {
                    $elemMatch: {
                        peopleNeeded: {
                            $elemMatch: {
                                //below attributes will be added just after as it
                                //skills: skillsFilter,
                                //teamId: neededTeamFilter
                            }
                        }
                    }
                }
            };
            if (skillsFilter)
                skillsAndNeededTeamFilterForAssigned.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.skills = {$all: skillsFilter};
            if (neededTeamFilter) {
                if (neededTeamFilter === "noNeededTeam")
                    skillsAndNeededTeamFilterForAssigned.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.teamId = null;
                else
                    skillsAndNeededTeamFilterForAssigned.timeSlots.$elemMatch.peopleNeededAssigned.$elemMatch.teamId = neededTeamFilter;
            }
        }
        var skillsAndNeededTeamFilter = {
            timeSlots: {
                $elemMatch: {
                    peopleNeeded: {
                        $elemMatch: {
                            assignedUserId:{ $eq: null }
                            //below attributes will be added just after as it
                            //skills: skillsFilter,
                            //teamId: neededTeamFilter
                        }
                    }
                }
            }
        };
        if (skillsFilter)
            skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.skills = {$all: skillsFilter};
        if (neededTeamFilter) {
            if (neededTeamFilter === "noNeededTeam")
                skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.teamId = null;
            else
                skillsAndNeededTeamFilter.timeSlots.$elemMatch.peopleNeeded.$elemMatch.teamId = neededTeamFilter;
        }

        var searchResult;
        var filterResult;

        if (displayAssignedTask && assignmentType === AssignmentType.TASKTOUSER) {
            filterResult = Tasks.find({
                $and: [
                    filter,
                    teamFilter,
                    {
                        $or: [
                            skillsAndNeededTeamFilter,
                            skillsAndNeededTeamFilterForAssigned
                        ]
                    }
                ]
            }, {limit: 20}).fetch();
        } else {
            filterResult = Tasks.find({
                $and: [
                    filter,
                    teamFilter,
                    skillsAndNeededTeamFilter,
                ]
            }, {limit: 20}).fetch();
        }

        searchResult = TasksIndex.search(filterIndex, {limit: 20}).fetch();
        return _.intersectionObjects(searchResult, filterResult);
    }

    team() {
        return Teams.findOne({_id: this.currentData().teamId}).name;
    }

    user() {
        return Users.findOne({_id: this.currentData().userId}).name;
    }

    timeSlotsInfo() {
        var task = this.currentData();
        var timeSlots = task.timeSlots;
        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];
            _.each(timeSlots, (timeSlot) => {
                var date = AssignmentReactiveVars.SelectedDate.get();
                var start = new moment(timeSlot.start);
                var end = new moment(timeSlot.end);
                if ((start.isBefore(date) || start.isSame(date)) &&
                    (end.isAfter(date) || end.isSame(date))) {
                    result.push(timeSlot);
                }
            });

            var string = " - "
            _.each(result, function (timeSlot) {
                string += new moment(timeSlot.start).format("H[h]mm") + " - " + new moment(timeSlot.end).format("H[h]mm");
            });
            return string;
        } else {
            return; //timeSlot infos can be directly see in the calendar
        }
    }

    timeSlots() {
        var task = this.currentData();
        var timeSlots = task.timeSlots;
        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];
            _.each(timeSlots, (timeSlot) => {
                var date = AssignmentReactiveVars.SelectedDate.get();
                var start = new moment(timeSlot.start);
                var end = new moment(timeSlot.end);
                if ((start.isBefore(date) || start.isSame(date)) &&
                    (end.isAfter(date) || end.isSame(date))) {
                    result.push(timeSlot);
                }
            });
            return result;
        } else {
            return []; //timeSlot infos can be directly see in the calendar
        }
    }

    peopleNeeded() {
        var peopleNeeded = this.currentData().peopleNeeded;

        if (AssignmentReactiveVars.CurrentAssignmentType.get() === AssignmentType.USERTOTASK) {
            var result = [];

            _.each(peopleNeeded, (peopleNeed) => {
                var selectedUser = Users.findOne(AssignmentReactiveVars.SelectedUser.get());

                //userId : if existing, selected user must be the one
                if (peopleNeed.userId) {
                    if (peopleNeed.userId === selectedUser._id) {
                        result.push(peopleNeed);
                        return;
                    }
                    return;
                }

                //teamId : if existing, selected user must at least have the required team
                if (peopleNeed.teamId) {
                    if (!_.contains(selectedUser.teams, peopleNeed.teamId)) {
                        return;
                    }
                }

                //if no skills required, we don't care about the user's skills
                if (peopleNeed.skills.length === 0) {
                    result.push(peopleNeed);
                    return;
                }

                //skills : if not empty, user must have all the required skill
                var userHaveAllRequiredSkills = true;
                _.each(peopleNeed.skills, (skill) => {
                    if (!_.contains(selectedUser.skills, skill)) {
                        userHaveAllRequiredSkills = false;
                    }
                });
                if (userHaveAllRequiredSkills) {
                    result.push(peopleNeed);
                    return;
                }
            });

            return result;

        } else {
            return peopleNeeded;
        }

        var skills = timeSlot.skills;
    }

    neededTeamOptions() {
        return {
            chooseLabel: "Choose a needed team",
        };
    }

    responsibleTeamOptions() {
        return {
            chooseLabel: "Choose a responsible team",
        };
    }

}

AssignmentTasksList.register("AssignmentTasksList");

<|endoftext|>


<commit_before>
require('../components/validated-payment-form-component');

App.ValidatedPaymentFormCheckoutComponent = App.ValidatedPaymentFormComponent.extend({

  credits: Ember.computed.alias('currentUser.walletBalance'),
  showPaymentFormFields: '0',
  total: Ember.computed.alias('currentUser.nextBox.total'),

  creditsCoverTotal: function () {
    var covered = this.get('credits') > this.get('total');
    this.set('showPaymentForm', !covered);
    return covered;
  }.property('credits', 'total'),

  showPaymentForm: function () {
    return this.get('showPaymentFormFields') === '1';
  }.property('showPaymentFormFields'),

  showPaymentFormChanged: function () {
    this.send('showPaymentFormChanged', this.get('showPaymentForm'));
  }.observes('showPaymentForm'),

  togglePaymentForm: function() {
    return this.set('showPaymentForm', this.get('showPaymentFormFields') === '1');
  }.observes('showPaymentFormFields'),

  walletBalance: function () {
    var walletBalance = this.get('credits') > 0;
    if ( !walletBalance ) this.set('showPaymentForm', true);
    return walletBalance;
  }.property('credits')

});
<commit_msg>minor<commit_after>
require('../components/validated-payment-form-component');

App.ValidatedPaymentFormCheckoutComponent = App.ValidatedPaymentFormComponent.extend({

  credits: Ember.computed.alias('currentUser.walletBalance'),
  showPaymentFormFields: '0',
  total: Ember.computed.alias('currentUser.nextBox.total'),

  creditsCoverTotal: function () {
    var covered = this.get('credits') > this.get('total');
    this.set('showPaymentForm', !covered);
    return covered;
  }.property('credits', 'total'),

  showPaymentForm: function () {
    return this.get('showPaymentFormFields') === '1';
  }.property('showPaymentFormFields'),

  togglePaymentForm: function() {
    return this.set('showPaymentForm', this.get('showPaymentFormFields') === '1');
  }.observes('showPaymentFormFields'),

  walletBalance: function () {
    var walletBalance = this.get('credits') > 0;
    if ( !walletBalance ) this.set('showPaymentForm', true);
    return walletBalance;
  }.property('credits')

});
<|endoftext|>


<commit_before>var net           = require('net')
  , url           = require('url')
  , EventEmitter2 = require('eventemitter2').EventEmitter2
  , portscanner   = require('portscanner')
  , environment   = require('./environment')
  , fieldsUtil    = require('./fields')
  ;

function TCPROS(nodeName, topicName, messageType) {
  if ((this instanceof TCPROS) === false) {
    return new TCPROS();
  }

  this.nodeName = nodeName;
  this.topicName = topicName;
  this.messageType = messageType;
}
TCPROS.prototype.__proto__ = EventEmitter2.prototype;

TCPROS.prototype.listen = function() {
  var that = this;

  var hostname = environment.getHostname()
  portscanner.findAPortNotInUse(9000, null, hostname, function(error, port) {
    var uriFields = { protocol: 'http', hostname: hostname, port: port }
      , uri       = url.format(uriFields)
      ;

    var server = net.createServer(function(socket) {
      socket.on('data', function(data) {
        var connectionHeader = getConnectionHeaderFromBuffer(data);

        var connectionHeaderResponse = {
          callerId : '/' + that.nodeName
        , topic    : '/' + that.topicName
        , type     : that.messageType.messageType
        , md5sum   : that.messageType.md5
        };
        var buffer = getBufferFromConnectionHeader(connectionHeaderResponse);
        socket.write(buffer);

        that.socket = socket;
        that.emit('connect');
      });
    });

    server.on('listening', function() {
      that.emit('listening', uri);
    });

    server.on('error', function(error) {
      that.emit('error', error);
    });

    server.listen(port, hostname);
  });
};

TCPROS.prototype.createClient = function(port, host, subscriber) {
  var that = this

  this.socket = net.createConnection(port, host)
  this.socket.on('data', function(data) {
    var connectionHeader = getConnectionHeaderFromBuffer(data)

    var message = getMessageFromBuffer(data, Message)
    if (message !== null) {
      that.emit('message', message)
    }
  })

  // Sends a Connection Header to the socket to route to the correct publisher.
  var nodeId      = '/' + subscriber.get('nodeId')
    , topic       = '/' + subscriber.get('topic')
    , Message     = subscriber.get('Message')
    , message     = new Message()
    , messageType = message.get('type')
    , md5sum      = message.get('md5sum')

  var connectionHeader = {
    callerId : nodeId
  , topic    : topic
  , type     : messageType
  , md5sum   : md5sum
  }

  var buffer = getBufferFromConnectionHeader(connectionHeader)
  this.socket.write(buffer)
}

TCPROS.prototype.publish = function(message) {
  var publish = function() {
    var messageBuffer = getBufferFromMessage(message);
    this.socket.write(messageBuffer);
  };

  if (!this.socket) {
    this.once('connect', publish);
  }
  else {
    publish();
  }
};

function getConnectionHeaderFromBuffer(buffer) {
  var connectionHeader = {}

  var bufferOffset = 0
  var headerLength = buffer.readUInt32LE(bufferOffset)
  bufferOffset += 4
  while (bufferOffset < headerLength) {
    var fieldLength = buffer.readUInt32LE(bufferOffset)
    bufferOffset += 4
    var fieldStart      = bufferOffset
      , fieldEnd        = fieldStart + fieldLength
      , field           = buffer.toString('utf8', fieldStart, fieldEnd)
      , fieldComponents = field.split('=')
      , fieldName       = fieldComponents[0]
      , fieldValue      = fieldComponents[1]
    bufferOffset += fieldLength

    if (fieldName === 'callerid') {
      connectionHeader.callerId = fieldValue
    }
    else if (fieldName === 'topic') {
      connectionHeader.topic = fieldValue
    }
    else if (fieldName === 'md5sum') {
      connectionHeader.md5sum = fieldValue
    }
    else if (fieldName === 'type') {
      connectionHeader.type = fieldValue
    }
    else if (fieldName === 'message_definition') {
      connectionHeader.message_definition = fieldValue
    }
    else if (fieldName === 'latching') {
      connectionHeader.latching = parseInt(fieldValue)
    }
  }

  return connectionHeader;
}

function getBufferFromConnectionHeader(connectionHeader) {
  var headers = [];

  var callerId = connectionHeader.callerId;
  if (callerId !== undefined) {
    headers.push({ key: 'callerid', value: callerId });
  }
  var topic = connectionHeader.topic;
  if (topic !== undefined) {
    headers.push({ key: 'topic', value: topic });
  }
  var type = connectionHeader.type;
  if (type !== undefined) {
    headers.push({ key: 'type', value: type });
  }
  var md5sum = connectionHeader.md5sum;
  if (md5sum !== undefined) {
    headers.push({ key: 'md5sum', value: md5sum });
  }

  var headerLength = 0;
  headers.forEach(function(header) {
    headerLength += header.key.length;
    headerLength += header.value.length;
    headerLength += 1;
    headerLength += 4;
  });

  var buffer       = new Buffer(headerLength + 4)
    , bufferOffset = 0
    ;
  buffer.writeUInt32LE(headerLength, bufferOffset);
  bufferOffset += 4;
  headers.forEach(function(header) {
    var headerKeyValue = header.key + '=' + header.value;
    buffer.writeUInt32LE(headerKeyValue.length, bufferOffset);
    bufferOffset += 4;
    bufferOffset += buffer.write(headerKeyValue, bufferOffset, 'ascii');
  });

  return buffer;
}

function getBufferFromMessage(message) {
  var bufferSize   = fieldsUtil.getMessageSize(message)
    , buffer       = new Buffer(bufferSize + 4)
    , bufferOffset = 0
    ;

  buffer.writeUInt32LE(bufferSize, bufferOffset);
  bufferOffset += 4;

  populateBufferFromMessage(message, buffer, bufferOffset);

  return buffer;
}

function populateBufferFromMessage(message, buffer, bufferOffset) {
  var fields = message.fields;

  fields.forEach(function(field) {
    var fieldValue = message[field.name];

    if (fieldsUtil.isPrimitive(field.type)) {
      fieldsUtil.serializePrimitive(field.type, fieldValue, buffer, bufferOffset);
      bufferOffset += fieldsUtil.getPrimitiveSize(field.type, fieldValue);
    }
    else if (fieldsUtil.isArray(field.type)) {
      buffer.writeUInt32LE(fieldValue.length, bufferOffset);
      bufferOffset += 4;

      var arrayType = fieldsUtil.getTypeOfArray(field.type);
      fieldValue.forEach(function(value) {
        if (fieldsUtil.isPrimitive(arrayType)) {
          fieldsUtil.serializePrimitive(arrayType, value, buffer, bufferOffset);
          bufferOffset += fieldsUtil.getPrimitiveSize(arrayType, value);
        }
        else if (fieldsUtil.isMessage(arrayType)) {
          populateBufferFromMessage(value, buffer, bufferOffset);
          bufferOffset += fieldsUtil.getMessageSize(value)
        }
      });
    }
    else if (fieldsUtil.isMessage(field.type)) {
      populateBufferFromMessage(fieldValue, buffer, bufferOffset)
      bufferOffset += fieldsUtil.getMessageSize(fieldValue)
    }
  });
}

function deserializeMessage(buffer) {
  var message            = new this.messageType()
    , headerLength       = 0
    , isConnectionHeader = false
    , messageLength      = buffer.readUInt32LE(0)
    , bufferOffset       = 4

  // If the initial length is less than the buffer, than a connection header was
  // sent first as part of this buffer.
  if (messageLength < buffer.length - bufferOffset) {
    headerLength = messageLength;
    bufferOffset += headerLength;
    messageLength = buffer.readUInt32LE(bufferOffset);
    bufferOffset += 4;
  }
  // Checks if a connection header by looking for required attribute 'type'.
  else {
    var bufferString = buffer.toString('utf8');
    if (bufferString.indexOf('type=') >= 0) {
      isConnectionHeader = true;
      message = null;
    }
  }

  if (!isConnectionHeader) {
    message = populateMessageFromBuffer(message, buffer, bufferOffset);
  }

  return message
}

function populateMessageFromBuffer(message, buffer, bufferOffset) {
  var fields = message.fields;

  fields.forEach(function(field) {
    var fieldValue = message[field.name];

    // If the field is a constant, do not read from buffer.
    if (fieldValue === null || fieldValue instanceof Object) {

      if (fieldsUtil.isPrimitive(field.type)) {
        fieldValue = fieldsUtil.deserializePrimitive(field.type, buffer, bufferOffset)
        bufferOffset += fieldsUtil.getPrimitiveSize(field.type, fieldValue)
      }
      else if (fieldsUtil.isArray(field.type)) {
        var array     = []
          , arraySize = buffer.readUInt32LE(bufferOffset)
          , arrayType = fieldsUtil.getTypeOfArray(field.type)
          ;
        bufferOffset += 4;

        for (var i = 0; i < arraySize; i++) {
          if (fieldsUtil.isPrimitive(arrayFieldType)) {
            var value = fieldsUtil.deserializePrimitive(arrayType, buffer, bufferOffset);
            bufferOffset += fieldsUtil.getPrimitiveSize(arrayType, value);
            array.push(value);
          }
          // STOPPED
          // Inner messages are not defined. They should either be defined
          // dynamically here or in messages.
          // Leaning towards message.js bringing in sub messages too.
          else if (fieldsUtil.isMessageType(arrayType)) {
            var ArrayMessage = message.getMessageForField(fieldName)
              , arrayMessage = new ArrayMessage()
            arrayMessage = populateMessageFromBuffer(arrayMessage, buffer, bufferOffset);
            bufferOffset += fieldsUtil.getMessageSize(arrayType, arrayMessage);
            array.push(arrayMessage);
          }
        }

        fieldValue = array
      }
      else if (fields.isMessage(field.type)) {
        var innerMessage = message.get(fieldName)
        fieldValue = populateMessageFromBuffer(innerMessage, buffer, bufferOffset)
        bufferOffset += fields.getFieldSize(fieldType, fieldValue)
      }

      var messageParam = {};
      messageParam[fieldName] = fieldValue;
      message.set(messageParam);
    }
  }

  return message;
}

module.exports = TCPROS;

<commit_msg>Commenting out subscribe code from TCPROS.<commit_after>var net           = require('net')
  , url           = require('url')
  , EventEmitter2 = require('eventemitter2').EventEmitter2
  , portscanner   = require('portscanner')
  , environment   = require('./environment')
  , fieldsUtil    = require('./fields')
  ;

function TCPROS(nodeName, topicName, messageType) {
  if ((this instanceof TCPROS) === false) {
    return new TCPROS();
  }

  this.nodeName = nodeName;
  this.topicName = topicName;
  this.messageType = messageType;
}
TCPROS.prototype.__proto__ = EventEmitter2.prototype;

TCPROS.prototype.listen = function() {
  var that = this;

  var hostname = environment.getHostname()
  portscanner.findAPortNotInUse(9000, null, hostname, function(error, port) {
    var uriFields = { protocol: 'http', hostname: hostname, port: port }
      , uri       = url.format(uriFields)
      ;

    var server = net.createServer(function(socket) {
      socket.on('data', function(data) {
        var connectionHeader = getConnectionHeaderFromBuffer(data);

        var connectionHeaderResponse = {
          callerId : '/' + that.nodeName
        , topic    : '/' + that.topicName
        , type     : that.messageType.messageType
        , md5sum   : that.messageType.md5
        };
        var buffer = getBufferFromConnectionHeader(connectionHeaderResponse);
        socket.write(buffer);

        that.socket = socket;
        that.emit('connect');
      });
    });

    server.on('listening', function() {
      that.emit('listening', uri);
    });

    server.on('error', function(error) {
      that.emit('error', error);
    });

    server.listen(port, hostname);
  });
};

// TCPROS.prototype.createClient = function(port, host, subscriber) {
//   var that = this

//   this.socket = net.createConnection(port, host)
//   this.socket.on('data', function(data) {
//     var connectionHeader = getConnectionHeaderFromBuffer(data)

//     var message = getMessageFromBuffer(data, Message)
//     if (message !== null) {
//       that.emit('message', message)
//     }
//   })

//   // Sends a Connection Header to the socket to route to the correct publisher.
//   var nodeId      = '/' + subscriber.get('nodeId')
//     , topic       = '/' + subscriber.get('topic')
//     , Message     = subscriber.get('Message')
//     , message     = new Message()
//     , messageType = message.get('type')
//     , md5sum      = message.get('md5sum')

//   var connectionHeader = {
//     callerId : nodeId
//   , topic    : topic
//   , type     : messageType
//   , md5sum   : md5sum
//   }

//   var buffer = getBufferFromConnectionHeader(connectionHeader)
//   this.socket.write(buffer)
// }

TCPROS.prototype.publish = function(message) {
  var publish = function() {
    var messageBuffer = getBufferFromMessage(message);
    this.socket.write(messageBuffer);
  };

  if (!this.socket) {
    this.once('connect', publish);
  }
  else {
    publish();
  }
};

function getConnectionHeaderFromBuffer(buffer) {
  var connectionHeader = {}

  var bufferOffset = 0
  var headerLength = buffer.readUInt32LE(bufferOffset)
  bufferOffset += 4
  while (bufferOffset < headerLength) {
    var fieldLength = buffer.readUInt32LE(bufferOffset)
    bufferOffset += 4
    var fieldStart      = bufferOffset
      , fieldEnd        = fieldStart + fieldLength
      , field           = buffer.toString('utf8', fieldStart, fieldEnd)
      , fieldComponents = field.split('=')
      , fieldName       = fieldComponents[0]
      , fieldValue      = fieldComponents[1]
    bufferOffset += fieldLength

    if (fieldName === 'callerid') {
      connectionHeader.callerId = fieldValue
    }
    else if (fieldName === 'topic') {
      connectionHeader.topic = fieldValue
    }
    else if (fieldName === 'md5sum') {
      connectionHeader.md5sum = fieldValue
    }
    else if (fieldName === 'type') {
      connectionHeader.type = fieldValue
    }
    else if (fieldName === 'message_definition') {
      connectionHeader.message_definition = fieldValue
    }
    else if (fieldName === 'latching') {
      connectionHeader.latching = parseInt(fieldValue)
    }
  }

  return connectionHeader;
}

function getBufferFromConnectionHeader(connectionHeader) {
  var headers = [];

  var callerId = connectionHeader.callerId;
  if (callerId !== undefined) {
    headers.push({ key: 'callerid', value: callerId });
  }
  var topic = connectionHeader.topic;
  if (topic !== undefined) {
    headers.push({ key: 'topic', value: topic });
  }
  var type = connectionHeader.type;
  if (type !== undefined) {
    headers.push({ key: 'type', value: type });
  }
  var md5sum = connectionHeader.md5sum;
  if (md5sum !== undefined) {
    headers.push({ key: 'md5sum', value: md5sum });
  }

  var headerLength = 0;
  headers.forEach(function(header) {
    headerLength += header.key.length;
    headerLength += header.value.length;
    headerLength += 1;
    headerLength += 4;
  });

  var buffer       = new Buffer(headerLength + 4)
    , bufferOffset = 0
    ;
  buffer.writeUInt32LE(headerLength, bufferOffset);
  bufferOffset += 4;
  headers.forEach(function(header) {
    var headerKeyValue = header.key + '=' + header.value;
    buffer.writeUInt32LE(headerKeyValue.length, bufferOffset);
    bufferOffset += 4;
    bufferOffset += buffer.write(headerKeyValue, bufferOffset, 'ascii');
  });

  return buffer;
}

function getBufferFromMessage(message) {
  var bufferSize   = fieldsUtil.getMessageSize(message)
    , buffer       = new Buffer(bufferSize + 4)
    , bufferOffset = 0
    ;

  buffer.writeUInt32LE(bufferSize, bufferOffset);
  bufferOffset += 4;

  populateBufferFromMessage(message, buffer, bufferOffset);

  return buffer;
}

function populateBufferFromMessage(message, buffer, bufferOffset) {
  var fields = message.fields;

  fields.forEach(function(field) {
    var fieldValue = message[field.name];

    if (fieldsUtil.isPrimitive(field.type)) {
      fieldsUtil.serializePrimitive(field.type, fieldValue, buffer, bufferOffset);
      bufferOffset += fieldsUtil.getPrimitiveSize(field.type, fieldValue);
    }
    else if (fieldsUtil.isArray(field.type)) {
      buffer.writeUInt32LE(fieldValue.length, bufferOffset);
      bufferOffset += 4;

      var arrayType = fieldsUtil.getTypeOfArray(field.type);
      fieldValue.forEach(function(value) {
        if (fieldsUtil.isPrimitive(arrayType)) {
          fieldsUtil.serializePrimitive(arrayType, value, buffer, bufferOffset);
          bufferOffset += fieldsUtil.getPrimitiveSize(arrayType, value);
        }
        else if (fieldsUtil.isMessage(arrayType)) {
          populateBufferFromMessage(value, buffer, bufferOffset);
          bufferOffset += fieldsUtil.getMessageSize(value)
        }
      });
    }
    else if (fieldsUtil.isMessage(field.type)) {
      populateBufferFromMessage(fieldValue, buffer, bufferOffset)
      bufferOffset += fieldsUtil.getMessageSize(fieldValue)
    }
  });
}

// function deserializeMessage(buffer) {
//   var message            = new this.messageType()
//     , headerLength       = 0
//     , isConnectionHeader = false
//     , messageLength      = buffer.readUInt32LE(0)
//     , bufferOffset       = 4

//   // If the initial length is less than the buffer, than a connection header was
//   // sent first as part of this buffer.
//   if (messageLength < buffer.length - bufferOffset) {
//     headerLength = messageLength;
//     bufferOffset += headerLength;
//     messageLength = buffer.readUInt32LE(bufferOffset);
//     bufferOffset += 4;
//   }
//   // Checks if a connection header by looking for required attribute 'type'.
//   else {
//     var bufferString = buffer.toString('utf8');
//     if (bufferString.indexOf('type=') >= 0) {
//       isConnectionHeader = true;
//       message = null;
//     }
//   }

//   if (!isConnectionHeader) {
//     message = populateMessageFromBuffer(message, buffer, bufferOffset);
//   }

//   return message
// }

// function populateMessageFromBuffer(message, buffer, bufferOffset) {
//   var fields = message.fields;

//   fields.forEach(function(field) {
//     var fieldValue = message[field.name];

//     // If the field is a constant, do not read from buffer.
//     if (fieldValue === null || fieldValue instanceof Object) {

//       if (fieldsUtil.isPrimitive(field.type)) {
//         fieldValue = fieldsUtil.deserializePrimitive(field.type, buffer, bufferOffset)
//         bufferOffset += fieldsUtil.getPrimitiveSize(field.type, fieldValue)
//       }
//       else if (fieldsUtil.isArray(field.type)) {
//         var array     = []
//           , arraySize = buffer.readUInt32LE(bufferOffset)
//           , arrayType = fieldsUtil.getTypeOfArray(field.type)
//           ;
//         bufferOffset += 4;

//         for (var i = 0; i < arraySize; i++) {
//           if (fieldsUtil.isPrimitive(arrayFieldType)) {
//             var value = fieldsUtil.deserializePrimitive(arrayType, buffer, bufferOffset);
//             bufferOffset += fieldsUtil.getPrimitiveSize(arrayType, value);
//             array.push(value);
//           }
//           // STOPPED
//           // Inner messages are not defined. They should either be defined
//           // dynamically here or in messages.
//           // Leaning towards message.js bringing in sub messages too.
//           else if (fieldsUtil.isMessageType(arrayType)) {
//             var ArrayMessage = message.getMessageForField(fieldName)
//               , arrayMessage = new ArrayMessage()
//             arrayMessage = populateMessageFromBuffer(arrayMessage, buffer, bufferOffset);
//             bufferOffset += fieldsUtil.getMessageSize(arrayType, arrayMessage);
//             array.push(arrayMessage);
//           }
//         }

//         fieldValue = array
//       }
//       else if (fields.isMessage(field.type)) {
//         var innerMessage = message.get(fieldName)
//         fieldValue = populateMessageFromBuffer(innerMessage, buffer, bufferOffset)
//         bufferOffset += fields.getFieldSize(fieldType, fieldValue)
//       }

//       var messageParam = {};
//       messageParam[fieldName] = fieldValue;
//       message.set(messageParam);
//     }
//   }

//   return message;
// }

module.exports = TCPROS;

<|endoftext|>


<commit_before>// @flow strict

import * as React from 'react';
import { View } from 'react-native';
import {
  StyleSheet,
  NetworkImage,
  Price,
  Text,
  Stars,
} from '@kiwicom/mobile-shared';
import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
import { Translation } from '@kiwicom/mobile-localization';
import idx from 'idx';
import { defaultTokens } from '@kiwicom/mobile-orbit';

import type { HotelDetailPreview_availability } from './__generated__/HotelDetailPreview_availability.graphql';
import { HotelDetailConsumer } from './HotelDetailPreviewContext';
import HotelReviewScore from '../../components/HotelReviewScore';

type Props = {|
  +availability: ?HotelDetailPreview_availability,
|};

export const HotelDetailPreview = ({ availability }: Props) => {
  const name = idx(availability, _ => _.hotel.name);
  const price = idx(availability, _ => _.price) || {};
  const image = idx(availability, _ => _.hotel.mainPhoto.thumbnailUrl);

  return (
    <HotelDetailConsumer>
      {({ containerWidth }) => (
        <View style={[styles.container, { width: containerWidth }]}>
          <View>
            <NetworkImage
              style={styles.image}
              source={{
                uri: image,
              }}
              resizeMode="contain"
            />
          </View>
          <View style={styles.description}>
            <Text style={styles.hotelName} numberOfLines={1}>
              <Translation passThrough={name} />
            </Text>
            <View style={styles.row}>
              <Stars
                rating={idx(availability, _ => _.hotel.rating.stars)}
                style={styles.stars}
              />
              <HotelReviewScore hotel={idx(availability, _ => _.hotel)} />
            </View>
            {price &&
              price.currency != null &&
              price.amount != null && (
                <Text style={styles.price}>
                  <Price currency={price.currency} amount={price.amount} />
                </Text>
              )}
          </View>
        </View>
      )}
    </HotelDetailConsumer>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    height: 60,
  },
  image: {
    width: 60,
    height: 60,
    borderRadius: 3,
    marginEnd: 10,
  },
  description: {
    flex: 1,
    justifyContent: 'space-between',
  },
  hotelName: {
    fontWeight: '500',
  },
  price: {
    color: defaultTokens.paletteProductNormal,
    fontWeight: 'bold',
    fontSize: 12,
    android: {
      lineHeight: 13,
    },
  },
  stars: {
    color: defaultTokens.paletteInkLight,
  },
  row: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
});

export default createFragmentContainer(
  HotelDetailPreview,
  graphql`
    fragment HotelDetailPreview_availability on HotelAvailability {
      price {
        amount
        currency
      }
      hotel {
        ...HotelReviewScore_hotel
        id
        name
        mainPhoto {
          thumbnailUrl
        }
        rating {
          stars
        }
      }
    }
  `,
);
<commit_msg>Hotels: align price correctly in hotel detail preview when stars are missing<commit_after>// @flow strict

import * as React from 'react';
import { View } from 'react-native';
import {
  StyleSheet,
  NetworkImage,
  Price,
  Text,
  Stars,
} from '@kiwicom/mobile-shared';
import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
import { Translation } from '@kiwicom/mobile-localization';
import idx from 'idx';
import { defaultTokens } from '@kiwicom/mobile-orbit';

import type { HotelDetailPreview_availability } from './__generated__/HotelDetailPreview_availability.graphql';
import { HotelDetailConsumer } from './HotelDetailPreviewContext';
import HotelReviewScore from '../../components/HotelReviewScore';

type Props = {|
  +availability: ?HotelDetailPreview_availability,
|};

export const HotelDetailPreview = ({ availability }: Props) => {
  const name = idx(availability, _ => _.hotel.name);
  const price = idx(availability, _ => _.price) || {};
  const image = idx(availability, _ => _.hotel.mainPhoto.thumbnailUrl);
  const stars = idx(availability, _ => _.hotel.rating.stars) || 0;
  return (
    <HotelDetailConsumer>
      {({ containerWidth }) => (
        <View style={[styles.container, { width: containerWidth }]}>
          <View>
            <NetworkImage
              style={styles.image}
              source={{
                uri: image,
              }}
              resizeMode="contain"
            />
          </View>
          <View style={styles.description}>
            <Text style={styles.hotelName} numberOfLines={1}>
              <Translation passThrough={name} />
            </Text>
            <View style={styles.row}>
              <View>
                {stars > 0 && <Stars rating={stars} style={styles.stars} />}
                {price &&
                  price.currency != null &&
                  price.amount != null && (
                    <Text style={styles.price}>
                      <Price currency={price.currency} amount={price.amount} />
                    </Text>
                  )}
              </View>
              <HotelReviewScore hotel={idx(availability, _ => _.hotel)} />
            </View>
          </View>
        </View>
      )}
    </HotelDetailConsumer>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    height: 60,
  },
  image: {
    width: 60,
    height: 60,
    borderRadius: 3,
    marginEnd: 10,
  },
  description: {
    flex: 1,
    justifyContent: 'space-between',
    alignSelf: 'baseline',
  },
  hotelName: {
    fontWeight: '500',
  },
  price: {
    color: defaultTokens.paletteProductNormal,
    fontWeight: 'bold',
    fontSize: 12,
    android: {
      lineHeight: 13,
    },
  },
  stars: {
    color: defaultTokens.paletteInkLight,
  },
  row: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
});

export default createFragmentContainer(
  HotelDetailPreview,
  graphql`
    fragment HotelDetailPreview_availability on HotelAvailability {
      price {
        amount
        currency
      }
      hotel {
        ...HotelReviewScore_hotel
        id
        name
        mainPhoto {
          thumbnailUrl
        }
        rating {
          stars
        }
      }
    }
  `,
);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { MapPopup } from 'wri-api-components';

import Button from 'components/ui/button/button-component';
import Dropdown from 'components/ui/dropdown/dropdown-component';

class Popup extends Component {
  render() {
    const {
      map,
      data,
      latlng,
      options,
      value,
      interactions,
      setInteractionSelected
    } = this.props;

    return (
      <MapPopup
        map={map}
        latlng={latlng}
        data={{ ...interactions, selected: value }}
      >

          {options &&
            options.length > 1 && (
              <Dropdown
                className="layer-selector"
                theme="theme-dropdown-native-plain"
                value={value}
                options={options}
                onChange={e => setInteractionSelected(e.target.value)}
                native
              />
            )}
          {options &&
            options.length === 1 && (

{options[0].label}

            )}

            {data &&
              data.map(d => (

{d.label}:

{d.value || 'n/a'}

              ))}

            <Button>Analyse</Button>

      </MapPopup>
    );
  }
}

Popup.propTypes = {
  map: PropTypes.object,
  data: PropTypes.array,
  options: PropTypes.array,
  value: PropTypes.string,
  setInteractionSelected: PropTypes.func,
  latlng: PropTypes.object,
  interactions: PropTypes.object
};

export default Popup;
<commit_msg>update tooltip when layers change<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { MapPopup } from 'wri-api-components';

import Button from 'components/ui/button/button-component';
import Dropdown from 'components/ui/dropdown/dropdown-component';

class Popup extends Component {
  render() {
    const {
      map,
      data,
      latlng,
      options,
      value,
      interactions,
      setInteractionSelected
    } = this.props;

    return (
      <MapPopup
        map={map}
        latlng={latlng}
        data={{ ...interactions, selected: value, options, data }}
      >

          {options &&
            options.length > 1 && (
              <Dropdown
                className="layer-selector"
                theme="theme-dropdown-native-plain"
                value={value}
                options={options}
                onChange={e => setInteractionSelected(e.target.value)}
                native
              />
            )}
          {options &&
            options.length === 1 && (

{options[0].label}

            )}

            {data &&
              data.map(d => (

{d.label}:

{d.value || 'n/a'}

              ))}

            <Button>Analyse</Button>

      </MapPopup>
    );
  }
}

Popup.propTypes = {
  map: PropTypes.object,
  data: PropTypes.array,
  options: PropTypes.array,
  value: PropTypes.string,
  setInteractionSelected: PropTypes.func,
  latlng: PropTypes.object,
  interactions: PropTypes.object
};

export default Popup;
<|endoftext|>


<commit_before>//! moment-timezone.js
//! version : 0.5.4
//! author : Tim Wood
//! license : MIT
//! github.com/moment/moment-timezone
!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["moment"],b):"object"==typeof module&&module.exports?module.exports=b(require("moment")):b(a.moment)}(this,function(a){"use strict";function b(a){return a>96?a-87:a>64?a-29:a-48}function c(a){var c,d=0,e=a.split("."),f=e[0],g=e[1]||"",h=1,i=0,j=1;for(45===a.charCodeAt(0)&&(d=1,j=-1),d;d<f.length;d++)c=b(f.charCodeAt(d)),i=60*i+c;for(d=0;d<g.length;d++)h/=60,c=b(g.charCodeAt(d)),i+=c*h;return i*j}function d(a){for(var b=0;b<a.length;b++)a[b]=c(a[b])}function e(a,b){for(var c=0;b>c;c++)a[c]=Math.round((a[c-1]||0)+6e4*a[c]);a[b-1]=1/0}function f(a,b){var c,d=[];for(c=0;c<b.length;c++)d[c]=a[b[c]];return d}function g(a){var b=a.split("|"),c=b[2].split(" "),g=b[3].split(""),h=b[4].split(" ");return d(c),d(g),d(h),e(h,g.length),{name:b[0],abbrs:f(b[1].split(" "),g),offsets:f(c,g),untils:h,population:0|b[5]}}function h(a){a&&this._set(g(a))}function i(a){var b=a.toTimeString(),c=b.match(/\([a-z ]+\)/i);c&&c[0]?(c=c[0].match(/[A-Z]/g),c=c?c.join(""):void 0):(c=b.match(/[A-Z]{3,5}/g),c=c?c[0]:void 0),"GMT"===c&&(c=void 0),this.at=+a,this.abbr=c,this.offset=a.getTimezoneOffset()}function j(a){this.zone=a,this.offsetScore=0,this.abbrScore=0}function k(a,b){for(var c,d;d=6e4*((b.at-a.at)/12e4|0);)c=new i(new Date(a.at+d)),c.offset===a.offset?a=c:b=c;return a}function l(){var a,b,c,d=(new Date).getFullYear()-2,e=new i(new Date(d,0,1)),f=[e];for(c=1;48>c;c++)b=new i(new Date(d,c,1)),b.offset!==e.offset&&(a=k(e,b),f.push(a),f.push(new i(new Date(a.at+6e4)))),e=b;for(c=0;4>c;c++)f.push(new i(new Date(d+c,0,1))),f.push(new i(new Date(d+c,6,1)));return f}function m(a,b){return a.offsetScore!==b.offsetScore?a.offsetScore-b.offsetScore:a.abbrScore!==b.abbrScore?a.abbrScore-b.abbrScore:b.zone.population-a.zone.population}function n(a,b){var c,e;for(d(b),c=0;c<b.length;c++)e=b[c],I[e]=I[e]||{},I[e][a]=!0}function o(a){var b,c,d,e=a.length,f={},g=[];for(b=0;e>b;b++){d=I[a[b].offset]||{};for(c in d)d.hasOwnProperty(c)&&(f[c]=!0)}for(b in f)f.hasOwnProperty(b)&&g.push(H[b]);return g}function p(){try{var a=Intl.DateTimeFormat().resolvedOptions().timeZone;if(a){var b=H[r(a)];if(b)return b;z("Moment Timezone found "+a+" from the Intl api, but did not have that data loaded.")}}catch(c){}var d,e,f,g=l(),h=g.length,i=o(g),k=[];for(e=0;e<i.length;e++){for(d=new j(t(i[e]),h),f=0;h>f;f++)d.scoreOffsetAt(g[f]);k.push(d)}return k.sort(m),k.length>0?k[0].zone.name:void 0}function q(a){return D&&!a||(D=p()),D}function r(a){return(a||"").toLowerCase().replace(/\//g,"_")}function s(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)d=a[b].split("|"),c=d[0],e=r(c),F[e]=a[b],H[e]=c,d[5]&&n(e,d[2].split(" "))}function t(a,b){a=r(a);var c,d=F[a];return d instanceof h?d:"string"==typeof d?(d=new h(d),F[a]=d,d):G[a]&&b!==t&&(c=t(G[a],t))?(d=F[a]=new h,d._set(c),d.name=H[a],d):null}function u(){var a,b=[];for(a in H)H.hasOwnProperty(a)&&(F[a]||F[G[a]])&&H[a]&&b.push(H[a]);return b.sort()}function v(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)c=a[b].split("|"),d=r(c[0]),e=r(c[1]),G[d]=e,H[d]=c[0],G[e]=d,H[e]=c[1]}function w(a){s(a.zones),v(a.links),A.dataVersion=a.version}function x(a){return x.didShowError||(x.didShowError=!0,z("moment.tz.zoneExists('"+a+"') has been deprecated in favor of !moment.tz.zone('"+a+"')")),!!t(a)}function y(a){return!(!a._a||void 0!==a._tzm)}function z(a){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(a)}function A(b){var c=Array.prototype.slice.call(arguments,0,-1),d=arguments[arguments.length-1],e=t(d),f=a.utc.apply(null,c);return e&&!a.isMoment(b)&&y(f)&&f.add(e.parse(f),"minutes"),f.tz(d),f}function B(a){return function(){return this._z?this._z.abbr(this):a.call(this)}}function C(a){return function(){return this._z=null,a.apply(this,arguments)}}if(void 0!==a.tz)return z("Moment Timezone "+a.tz.version+" was already loaded "+(a.tz.dataVersion?"with data from ":"without any data")+a.tz.dataVersion),a;var D,E="0.5.4",F={},G={},H={},I={},J=a.version.split("."),K=+J[0],L=+J[1];(2>K||2===K&&6>L)&&z("Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js "+a.version+". See momentjs.com"),h.prototype={_set:function(a){this.name=a.name,this.abbrs=a.abbrs,this.untils=a.untils,this.offsets=a.offsets,this.population=a.population},_index:function(a){var b,c=+a,d=this.untils;for(b=0;b<d.length;b++)if(c<d[b])return b},parse:function(a){var b,c,d,e,f=+a,g=this.offsets,h=this.untils,i=h.length-1;for(e=0;i>e;e++)if(b=g[e],c=g[e+1],d=g[e?e-1:e],c>b&&A.moveAmbiguousForward?b=c:b>d&&A.moveInvalidForward&&(b=d),f<h[e]-6e4*b)return g[e];return g[i]},abbr:function(a){return this.abbrs[this._index(a)]},offset:function(a){return this.offsets[this._index(a)]}},j.prototype.scoreOffsetAt=function(a){this.offsetScore+=Math.abs(this.zone.offset(a.at)-a.offset),this.zone.abbr(a.at).replace(/[^A-Z]/g,"")!==a.abbr&&this.abbrScore++},A.version=E,A.dataVersion="",A._zones=F,A._links=G,A._names=H,A.add=s,A.link=v,A.load=w,A.zone=t,A.zoneExists=x,A.guess=q,A.names=u,A.Zone=h,A.unpack=g,A.unpackBase60=c,A.needsOffset=y,A.moveInvalidForward=!0,A.moveAmbiguousForward=!1;var M=a.fn;a.tz=A,a.defaultZone=null,a.updateOffset=function(b,c){var d,e=a.defaultZone;void 0===b._z&&(e&&y(b)&&!b._isUTC&&(b._d=a.utc(b._a)._d,b.utc().add(e.parse(b),"minutes")),b._z=e),b._z&&(d=b._z.offset(b),Math.abs(d)<16&&(d/=60),void 0!==b.utcOffset?b.utcOffset(-d,c):b.zone(d,c))},M.tz=function(b){return b?(this._z=t(b),this._z?a.updateOffset(this):z("Moment Timezone has no data for "+b+". See http://momentjs.com/timezone/docs/#/data-loading/."),this):this._z?this._z.name:void 0},M.zoneName=B(M.zoneName),M.zoneAbbr=B(M.zoneAbbr),M.utc=C(M.utc),a.tz.setDefault=function(b){return(2>K||2===K&&9>L)&&z("Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js "+a.version+"."),a.defaultZone=b?t(b):null,a};var N=a.momentProperties;return"[object Array]"===Object.prototype.toString.call(N)?(N.push("_z"),N.push("_a")):N&&(N._z=null),a});<commit_msg>Delete moment-timezone.min.js<commit_after><|endoftext|>


<commit_before>import os from 'os';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import stream from 'stream';
import Promise from 'bluebird';
import CustomError from 'customerror';
import AWS from 'aws-sdk';
import _ from 'lodash';
import type from 'type-of';

Promise.promisifyAll(fs);

class BloodySimpleS3 {

  constructor(options) {
    if (!_.isPlainObject(options)) {
      throw new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument');
    }

    options = _.defaults(options, {
      region: 'us-east-1',
      sslEnabled: true
    });

    if (!_.isString(options.bucket)) {
      throw new CustomError(`Invalid bucket option; expected string, received ${type(options.bucket)}`, 'InvalidArgument');
    }

    if (!_.isString(options.accessKeyId)) {
      throw new CustomError(`Invalid accessKeyId option; expected string, received ${type(options.accessKeyId)}`, 'InvalidArgument');
    }

    if (!_.isString(options.secretAccessKey)) {
      throw new CustomError(`Invalid secretAccessKey option; expected string, received ${type(options.secretAccessKey)}`, 'InvalidArgument');
    }

    if (!_.isString(options.region)) {
      throw new CustomError(`Invalid region option; expected string, received ${type(options.region)}`, 'InvalidArgument');
    }

    if (!_.isBoolean(options.sslEnabled)) {
      throw new CustomError(`Invalid sslEnabled option; expected boolean, received ${type(options.sslEnabled)}`, 'InvalidArgument');
    }

    this.bucket = options.bucket;

    this.s3 = new AWS.S3({
      accessKeyId: options.accessKeyId,
      secretAccessKey: options.secretAccessKey,
      region: options.region,
      sslEnabled: options.sslEnabled,
      apiVersion: '2006-03-01'
    });
  }

  createReadStream(filename) {
    if (!_.isString(filename)) {
      throw new CustomError('Invalid filename param; expected string, received ' + type(filename), 'InvalidArgument');
    }

    const params = {
      Key: filename,
      Bucket: this.bucket
    };

    return this.s3.getObject(params).createReadStream();
  }

  downloadWithoutCheck(source, target, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(target)) {
      callback = target;
      target = os.tmpdir();
    } else if (_.isUndefined(target)) {
      target = os.tmpdir();
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    return fs.statAsync(target)

      .then((stat) => {
        if (stat.isDirectory()) return path.join(target, path.basename(source));
        if (stat.isFile()) return target;

        throw new CustomError('Invalid target path; expected directory or file', 'InvalidDownloadTarget');
      })

      .then((file) => {
        const resolver = (resolve, reject) => {
          const readable = this.createReadStream(source);
          const writable = fs.createWriteStream(file);
          readable.pipe(writable);

          readable.on('error', reject);

          writable.on('finish', () => {
            resolve({name: file});
          });
        };

        return new Promise(resolver);
      })

      .nodeify(callback);
  }

  download(source, target, callback) {
    return Promise.props({
      file: this.downloadWithoutCheck(source, target),
      meta: this.getFileMeta(source)
    })

      .then((props) => {
        return fs.readFileAsync(props.file.name)

          .then((buf) => {
            if (props.meta.ETag !== `"${crypto.createHash('md5').update(buf).digest().toString('hex')}"`) {
              return fs.unlinkAsync(props.file.name) // remove downloaded file from disk
                .throw(new CustomError(`Invalid checksum of file ${source}`, 'BadMD5Digest'));
            }

            return props.file;
          });
      })
      .nodeify(callback);
  }

  readBuffer(filename, callback) {
    const resolver = (resolve, reject) => {
      const readable = this.createReadStream(filename);
      const buf = [];

      readable.on('data', (d) => buf.push(d));
      readable.on('end', () => {
        resolve(Buffer.concat(buf));
      });

      readable.on('error', reject);
    };

    return new Promise(resolver).nodeify(callback);
  }

  writeFile(filename, contents, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (!(contents instanceof stream.Readable) && !(Buffer.isBuffer(contents)) && !_.isString(contents)) {
      return Promise.reject(new CustomError(`Invalid contents param; expected readable stream, buffer or string, received ${type(contents)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Key: filename,
      Body: contents,
      Bucket: this.bucket
    };

    if (Buffer.isBuffer(contents)) {
      params.ContentMD5 = crypto.createHash('md5').update(contents).digest().toString('base64'); // force integrity check
    }

    const resolver = (resolve, reject) => {
      this.s3.putObject(params, (err) => {
        if (err) return reject(err);
        resolve({name: filename});
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  upload(source, target, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    source = path.resolve(__dirname, source);

    if (_.isFunction(target)) {
      callback = target;
      target = path.basename(source);
    } else if (_.isUndefined(target)) {
      target = path.basename(source);
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    return fs.statAsync(source)

      .then((stat) => {
        if (!stat.isFile()) {
          throw new CustomError('Source is invalid; you must reference a file', 'InvalidUploadSource');
        }

        if (stat.size < os.freemem()) {
          return fs.readFileAsync(source); // memory is suffient - use buffer
        }

        return fs.createReadStream(source);
      })

      .then((contents) => {
        return this.writeFile(target, contents);
      })

      .nodeify(callback);
  }

  list(dir, options, callback) {
    if (!_.isString(dir)) {
      return Promise.reject(new CustomError(`Invalid dir param; expected string, received ${type(dir)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(options)) {
      callback = options;
      options = {};
    } else if (_.isUndefined(options)) {
      options = {};
    }

    if (!_.isPlainObject(options)) {
      return Promise.reject(new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Bucket: this.bucket,
      Prefix: path.normalize(dir),
      Marker: options.cursor || path.normalize(dir),
      MaxKeys: options.limit
    };

    const resolver = (resolve, reject) => {
      this.s3.listObjects(params, (err, data) => {
        if (err) return reject(err);

        const arr = _.map(data.Contents, (obj) => {
          return {
            name: obj.Key,
            size: obj.Size,
            last_modified: obj.LastModified
          };
        });

        resolve(arr);
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  copy(source, target, options, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(options)) {
      callback = options;
      options = {};
    } else if (_.isUndefined(options)) {
      options = {};
    }

    if (!_.isPlainObject(options)) {
      return Promise.reject(new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = _.assign(options, {
      Bucket: this.bucket,
      CopySource: encodeURIComponent(path.join(this.bucket, source)),
      Key: target,
      MetadataDirective: 'COPY'
    });

    const resolver = (resolve, reject) => {
      this.s3.copyObject(params, (err, data) => {
        if (err) return reject(err);
        resolve({name: target, last_modified: data.LastModified});
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  remove(filename, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Bucket: this.bucket,
      Key: filename
    };

    const resolver = (resolve, reject) => {
      this.s3.deleteObject(params, (err) => {
        if (err) return reject(err);
        resolve();
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  move(source, target, options, callback) {
    if (_.isFunction(options)) {
      callback = options;
      options = {};
    }

    return this.copy(source, target, options)

      .bind(this)

      .then((data) => {
        return this.remove(source).return(data);
      })

      .nodeify(callback);
  }

  getFileMeta(filename, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Key: filename,
      Bucket: this.bucket
    };

    const resolver = (resolve, reject) => {
      this.s3.headObject(params, (err, data) => {
        if (err) return reject(err);
        resolve(data);
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

}

// BloodySimpleS3.prototype.rename = BloodySimpleS3.prototype.move;

export default BloodySimpleS3;
<commit_msg>Reinstantiate #rename() as alias to #move<commit_after>import os from 'os';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import stream from 'stream';
import Promise from 'bluebird';
import CustomError from 'customerror';
import AWS from 'aws-sdk';
import _ from 'lodash';
import type from 'type-of';

Promise.promisifyAll(fs);

class BloodySimpleS3 {

  constructor(options) {
    if (!_.isPlainObject(options)) {
      throw new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument');
    }

    options = _.defaults(options, {
      region: 'us-east-1',
      sslEnabled: true
    });

    if (!_.isString(options.bucket)) {
      throw new CustomError(`Invalid bucket option; expected string, received ${type(options.bucket)}`, 'InvalidArgument');
    }

    if (!_.isString(options.accessKeyId)) {
      throw new CustomError(`Invalid accessKeyId option; expected string, received ${type(options.accessKeyId)}`, 'InvalidArgument');
    }

    if (!_.isString(options.secretAccessKey)) {
      throw new CustomError(`Invalid secretAccessKey option; expected string, received ${type(options.secretAccessKey)}`, 'InvalidArgument');
    }

    if (!_.isString(options.region)) {
      throw new CustomError(`Invalid region option; expected string, received ${type(options.region)}`, 'InvalidArgument');
    }

    if (!_.isBoolean(options.sslEnabled)) {
      throw new CustomError(`Invalid sslEnabled option; expected boolean, received ${type(options.sslEnabled)}`, 'InvalidArgument');
    }

    this.bucket = options.bucket;

    this.s3 = new AWS.S3({
      accessKeyId: options.accessKeyId,
      secretAccessKey: options.secretAccessKey,
      region: options.region,
      sslEnabled: options.sslEnabled,
      apiVersion: '2006-03-01'
    });
  }

  createReadStream(filename) {
    if (!_.isString(filename)) {
      throw new CustomError('Invalid filename param; expected string, received ' + type(filename), 'InvalidArgument');
    }

    const params = {
      Key: filename,
      Bucket: this.bucket
    };

    return this.s3.getObject(params).createReadStream();
  }

  downloadWithoutCheck(source, target, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(target)) {
      callback = target;
      target = os.tmpdir();
    } else if (_.isUndefined(target)) {
      target = os.tmpdir();
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    return fs.statAsync(target)

      .then((stat) => {
        if (stat.isDirectory()) return path.join(target, path.basename(source));
        if (stat.isFile()) return target;

        throw new CustomError('Invalid target path; expected directory or file', 'InvalidDownloadTarget');
      })

      .then((file) => {
        const resolver = (resolve, reject) => {
          const readable = this.createReadStream(source);
          const writable = fs.createWriteStream(file);
          readable.pipe(writable);

          readable.on('error', reject);

          writable.on('finish', () => {
            resolve({name: file});
          });
        };

        return new Promise(resolver);
      })

      .nodeify(callback);
  }

  download(source, target, callback) {
    return Promise.props({
      file: this.downloadWithoutCheck(source, target),
      meta: this.getFileMeta(source)
    })

      .then((props) => {
        return fs.readFileAsync(props.file.name)

          .then((buf) => {
            if (props.meta.ETag !== `"${crypto.createHash('md5').update(buf).digest().toString('hex')}"`) {
              return fs.unlinkAsync(props.file.name) // remove downloaded file from disk
                .throw(new CustomError(`Invalid checksum of file ${source}`, 'BadMD5Digest'));
            }

            return props.file;
          });
      })
      .nodeify(callback);
  }

  readBuffer(filename, callback) {
    const resolver = (resolve, reject) => {
      const readable = this.createReadStream(filename);
      const buf = [];

      readable.on('data', (d) => buf.push(d));
      readable.on('end', () => {
        resolve(Buffer.concat(buf));
      });

      readable.on('error', reject);
    };

    return new Promise(resolver).nodeify(callback);
  }

  writeFile(filename, contents, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (!(contents instanceof stream.Readable) && !(Buffer.isBuffer(contents)) && !_.isString(contents)) {
      return Promise.reject(new CustomError(`Invalid contents param; expected readable stream, buffer or string, received ${type(contents)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Key: filename,
      Body: contents,
      Bucket: this.bucket
    };

    if (Buffer.isBuffer(contents)) {
      params.ContentMD5 = crypto.createHash('md5').update(contents).digest().toString('base64'); // force integrity check
    }

    const resolver = (resolve, reject) => {
      this.s3.putObject(params, (err) => {
        if (err) return reject(err);
        resolve({name: filename});
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  upload(source, target, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    source = path.resolve(__dirname, source);

    if (_.isFunction(target)) {
      callback = target;
      target = path.basename(source);
    } else if (_.isUndefined(target)) {
      target = path.basename(source);
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    return fs.statAsync(source)

      .then((stat) => {
        if (!stat.isFile()) {
          throw new CustomError('Source is invalid; you must reference a file', 'InvalidUploadSource');
        }

        if (stat.size < os.freemem()) {
          return fs.readFileAsync(source); // memory is suffient - use buffer
        }

        return fs.createReadStream(source);
      })

      .then((contents) => {
        return this.writeFile(target, contents);
      })

      .nodeify(callback);
  }

  list(dir, options, callback) {
    if (!_.isString(dir)) {
      return Promise.reject(new CustomError(`Invalid dir param; expected string, received ${type(dir)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(options)) {
      callback = options;
      options = {};
    } else if (_.isUndefined(options)) {
      options = {};
    }

    if (!_.isPlainObject(options)) {
      return Promise.reject(new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Bucket: this.bucket,
      Prefix: path.normalize(dir),
      Marker: options.cursor || path.normalize(dir),
      MaxKeys: options.limit
    };

    const resolver = (resolve, reject) => {
      this.s3.listObjects(params, (err, data) => {
        if (err) return reject(err);

        const arr = _.map(data.Contents, (obj) => {
          return {
            name: obj.Key,
            size: obj.Size,
            last_modified: obj.LastModified
          };
        });

        resolve(arr);
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  copy(source, target, options, callback) {
    if (!_.isString(source)) {
      return Promise.reject(new CustomError(`Invalid source param; expected string, received ${type(source)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (!_.isString(target)) {
      return Promise.reject(new CustomError(`Invalid target param; expected string, received ${type(target)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    if (_.isFunction(options)) {
      callback = options;
      options = {};
    } else if (_.isUndefined(options)) {
      options = {};
    }

    if (!_.isPlainObject(options)) {
      return Promise.reject(new CustomError(`Invalid options param; expected object, received ${type(options)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = _.assign(options, {
      Bucket: this.bucket,
      CopySource: encodeURIComponent(path.join(this.bucket, source)),
      Key: target,
      MetadataDirective: 'COPY'
    });

    const resolver = (resolve, reject) => {
      this.s3.copyObject(params, (err, data) => {
        if (err) return reject(err);
        resolve({name: target, last_modified: data.LastModified});
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  remove(filename, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Bucket: this.bucket,
      Key: filename
    };

    const resolver = (resolve, reject) => {
      this.s3.deleteObject(params, (err) => {
        if (err) return reject(err);
        resolve();
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

  move(source, target, options, callback) {
    if (_.isFunction(options)) {
      callback = options;
      options = {};
    }

    return this.copy(source, target, options)

      .bind(this)

      .then((data) => {
        return this.remove(source).return(data);
      })

      .nodeify(callback);
  }

  rename() {
    return this.move.apply(this, arguments);
  }

  getFileMeta(filename, callback) {
    if (!_.isString(filename)) {
      return Promise.reject(new CustomError(`Invalid filename param; expected string, received ${type(filename)}`, 'InvalidArgument'))
        .nodeify(callback);
    }

    const params = {
      Key: filename,
      Bucket: this.bucket
    };

    const resolver = (resolve, reject) => {
      this.s3.headObject(params, (err, data) => {
        if (err) return reject(err);
        resolve(data);
      });
    };

    return new Promise(resolver).nodeify(callback);
  }

}

export default BloodySimpleS3;
<|endoftext|>