<commit_before>const moment = require('moment');

const db = require('../orm.js');
const config = require('../../config');
const email = require('../email.js');
const log = require('../logging.js');
const updateAssociation = require('./util.js').updateAssociation;
const Op = db.Sequelize.Op;
const wrapper = require('../errors');
const _ = require('lodash');

import feeFactory from '../../shared/fee/feeFactory';

const bookings = {};

bookings.getUserBookings = (req, res) => {

    db.booking.findAll({
                           where:
                               {
                                   $or:
                                       [{guestUUID: req.cookies.guestUUID},
                                           {
                                               $and:
                                                   [{userId: req.user.id},
                                                       {userId: {$not: 1}}
                                                   ]
                                           }]
                               }, include: [{model: db.participant}, {model: db.payment}]
                       })
    .then(bookings => {
        let data = {bookings};
        res.json(data);
    });
};

const getUserScopes = (user, event, participants = true) => {

    const scopes = [];

    if (user.roles.find(role => role.name === "admin") || user.id === event.userId) scopes.push({method: ['Limited', event.id, null, null, participants ? 'defaultScope': null, true]});

    user.roles.filter(r => r.eventId === event.id && r.name !== 'book').forEach(r => {

        let participantScope = null;
        let includePayments = false;
        switch (r.name) {
            case "KP":
                participantScope = "KP";
                break;
            case "Money":
                participantScope = "Money";
                includePayments = true;
                break;
            default:
                participantScope = "defaultScope";
        }

        scopes.push({method: ['Limited', event.id, r.villageId, r.organisationId, participants ? participantScope : null, includePayments]})
    });

    return scopes;

};

bookings.getEventBookings = async function (req, res) {
    //need to loop over a users roles and assemble the data they are allowed to see
    const event = await db.event.findOne({where: {id: {[Op.eq]: req.params.eventId}}});

    const flat = await getBookingsAndCombineScopes(req.user, event)

    res.json({bookings: flat});

};

bookings.getBooking = (req, res) => {
    db.booking.findOne({where: {id: req.params.bookingId}, include: [{model: db.participant}, {model: db.event}, {model: db.payment}]})
    .then(booking => {
        let data = {};
        data.bookings = [booking];
        res.json(data);
    });
};

bookings.createBooking = (req, res) => {

    let newBooking = req.body;
    newBooking.guestUUID = req.cookies.guestUUID;
    newBooking.userId = newBooking.userId || req.user.id;
    newBooking.maxParticipants = newBooking.participants.length;
    newBooking.participants.forEach(p => {
        delete p.internalExtra
    });

    db.booking.create(newBooking, {
        include: [{
            association: 'participants'
        }]
    }).then((booking) => {
        return db.booking.findOne({where: {id: booking.id}, include: [{model: db.participant}, {model: db.event}]})
    }).then(booking => {
        log.log("debug", "Created new booking id %s for %s", booking.id, booking.userName);
        let data = {};
        data.bookings = [booking];
        res.json(data);

        //send an e-mail
        const fees = feeFactory(booking.event);
        const emailData = booking.get({plain: true});
        emailData.editURL = config.BASE_PATH + '/' + (emailData.userId === 1 ? "guestUUID/" + emailData.eventId + "/" + emailData.guestUUID : "event/" + emailData.eventId + "/book");
        emailData.user = req.user;
        email.single(booking.userEmail, 'confirmation', emailData);
        email.toManagers('managerBookingCreated', emailData);

        return null;//Don't want the request to wait on e-amil promise
    }).catch(e => {
        console.log(e);
        res.status(500).end();
    });
};

/*
bookings.editBooking = (req, res) => {
    Booking.findOne({ where: { id: req.body.id } })
        .then(booking => {
            booking.userName = req.body.user.name;
            booking.userEmail = req.body.user.email;
            booking.userContact = req.body.user.phone;
            booking.paymentType = req.body.paymentType;
            booking.emergencyName = req.body.emergencyName;
            booking.emergencyPhone = req.body.emergencyPhone;
            booking.note = req.body.note;
            Participant.destroy({ where: { bookingId: req.body.id } })//delete previous partiticpants
                .then(() => Promise.all(
                    req.body.participants.map(p => {
                        p.bookingId = req.body.id;
                        return Participant.upsert(p); //insert new ones
                    })))
                .then(() => booking.save())
                .then(() => Booking.findOne({ where: { id: req.body.id }, include: [{ model: Participant }] }))
                .then((booking) => {
                    let data = {};
                    data[booking.id] = booking;
                    res.json(data);
                });
        });
}
*/

bookings.editBooking = (req, res) => {

    delete req.body.internalExtra; //delete the internal extra so user can't update.
    delete req.body.payments
    req.body.participants.forEach(p => {
        delete p.internalExtra
    });
    db.booking.findOne({where: {id: req.body.id}})
    .then(booking => {
        req.body.maxParticipants = Math.max(booking.maxParticipants || 0, req.body.participants.length);
        return booking.update(req.body)//this ignores partitipants!
    })
    .then(booking => db.booking.findOne({where: {id: booking.id}, include: [{model: db.participant}]}))
    .then(booking => updateAssociation(booking, 'participants', db.participant, req.body.participants))
    .then(() => db.booking.findOne({where: {id: req.body.id}, include: [{model: db.participant}, {model: db.payment}, {model:db.event}]}))
    .then(booking => {
        log.log("debug", "User %s Editing Booking id %s", req.user.userName, booking.id);
        let data = {};
        data.bookings = [booking];
        res.json(data);

        if(req.user.id === booking.userId) {
            const fees = feeFactory(booking.event);
            const emailData = booking.get({plain: true});
            emailData.editURL = config.BASE_PATH + '/' + (emailData.userId === 1 ? "guestUUID/" + emailData.eventId + "/" + emailData.guestUUID : "event/" + emailData.eventId + "/book");
            emailData.user = req.user;
            email.single(booking.userEmail, 'updated', emailData);
            email.toManagers('managerBookingUpdated', emailData);
        }
    });
};

bookings.togglePaid = (req, res) => {
    db.booking.findOne({where: {id: req.body.id}, include: [{model: db.participant}]})
    .then(booking => {
        booking.paid = !booking.paid;
        return booking.save()
    })
    .then((booking) => {
        let data = {};
        data.bookings = [booking];
        res.json(data);
    });
};

bookings.deleteBooking = (req, res) => {
    db.booking.findOne({where: {id: req.body.id},include: [{model:db.event}, {model: db.participant}]})
    .then(booking => {
        const emailData = booking.get({plain: true});
        emailData.user = req.user;
        //email.single(booking.userEmail, 'updated', emailData);
        email.toManagers('managerBookingDeleted', emailData);
        return booking.destroy()
    })
    .then(() => db.booking.findAll({
                                       where:
                                           {
                                               $or:
                                                   [{guestUUID: req.cookies.guestUUID},
                                                       {
                                                           $and:
                                                               [{userId: req.user.id},
                                                                   {userId: {$not: 1}}
                                                               ]
                                                       }]
                                           }, include: [{model: db.participant}]
                                   }))
    .then(bookings => {
        let data = {bookings};
        res.json(data);
    });
};

bookings.assignVillage = async function (req, res) {
    const booking = await db.booking.findOne({where: {id: req.body.bookingId}, include: [{model: db.participant}]});
    booking.villageId = req.body.villageId;
    await booking.save();
    const data = {};
    data.bookings = [booking];
    res.json(data);
};

bookings.addPayment = async function (req, res, next) {

    await db.payment.create(req.body);

    const booking = await db.booking.findOne({
                                                 where:   {id: {[Op.eq]: req.body.bookingId}},
                                                 include: [{model: db.event}]
                                             }); //get the booking, but we can't send this as dangerous scope.

    const bookings = await getBookingAndCombineScopes(req.user, booking)

    res.json({bookings: bookings});
};

bookings.deletePayment = async function (req, res, next) {

    const payment = await db.payment.findOne({where: {id: {[Op.eq]: req.body.id}}});

    const booking = await db.booking.findOne({
                                                 where:   {id: {[Op.eq]: payment.bookingId}},
                                                 include: [{model: db.event}]
                                             }); //get the booking, but we can't send this as dangerous scope.

    await payment.destroy();

    const bookings = await getBookingAndCombineScopes(req.user, booking)

    res.json({bookings: bookings});
};

bookings.approveMembership = async function (req, res, next) {

    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, member: true};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.unapproveMembership = async function (req, res, next) {
    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, member: false};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.approveDBS = async function (req, res, next) {

    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, dbs: true};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.unapproveDBS = async function (req, res, next) {
    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, dbs: false};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

/**
 *  Test function to update a paricipants updatedAt, disabled in production enviroments.
 *
 */

bookings.updateParticipantDate = async function (req, res) {

    const results = await db.sequelize.query('UPDATE participants SET "updatedAt" = $date WHERE id = $id',
                                             {
                                                 bind: {
                                                     id:   req.params.participantId,
                                                     date: moment(req.params.date).format('YYYY-MM-DD HH:mm:ss.SSS')
                                                 },
                                                 type: db.sequelize.QueryTypes.UPDATE
                                             });

    const paricipant = await db.participant.findOne({where: {id: req.params.participantId}});

    res.json(paricipant);

};

const getBookingAndCombineScopes = async function (user, booking) {

    const scopes = getUserScopes(user, booking.event);
    const results = await Promise.all(scopes.map(s => db.booking.scope(s).findOne({where: {id: booking.id}})));

    const obj = results.filter(r => r).reduce((a, c) => {

        _.merge(a, c.get({plain: true}))
        return a

    }, {});

    return [obj]
}

const getBookingsAndCombineScopes = async function (user, event) {
    const scopes = getUserScopes(user, event);
    console.log(`getting details for ${scopes.length} scopes`)
    console.log(scopes)
    const results = await Promise.all(scopes.map(s => db.booking.scope(s).findAll()));

    const obj = results.filter(r => r).reduce((a, c) => {

        c.forEach(b => {

            a[b.id] = a[b.id] || {}

            _.merge(a[b.id], b.get({plain: true}))

        });

        return a

    }, {});

    const flat = []

    for (let key in obj) flat.push(obj[key])

    return flat
}

module.exports = wrapper(bookings);

<commit_msg>Add logging here<commit_after>const moment = require('moment');

const db = require('../orm.js');
const config = require('../../config');
const email = require('../email.js');
const log = require('../logging.js');
const updateAssociation = require('./util.js').updateAssociation;
const Op = db.Sequelize.Op;
const wrapper = require('../errors');
const _ = require('lodash');

import feeFactory from '../../shared/fee/feeFactory';

const bookings = {};

bookings.getUserBookings = (req, res) => {

    db.booking.findAll({
                           where:
                               {
                                   $or:
                                       [{guestUUID: req.cookies.guestUUID},
                                           {
                                               $and:
                                                   [{userId: req.user.id},
                                                       {userId: {$not: 1}}
                                                   ]
                                           }]
                               }, include: [{model: db.participant}, {model: db.payment}]
                       })
    .then(bookings => {
        let data = {bookings};
        res.json(data);
    });
};

const getUserScopes = (user, event, participants = true) => {

    const scopes = [];

    if (user.roles.find(role => role.name === "admin") || user.id === event.userId) scopes.push({method: ['Limited', event.id, null, null, participants ? 'defaultScope': null, true]});

    user.roles.filter(r => r.eventId === event.id && r.name !== 'book').forEach(r => {

        let participantScope = null;
        let includePayments = false;
        switch (r.name) {
            case "KP":
                participantScope = "KP";
                break;
            case "Money":
                participantScope = "Money";
                includePayments = true;
                break;
            default:
                participantScope = "defaultScope";
        }

        scopes.push({method: ['Limited', event.id, r.villageId, r.organisationId, participants ? participantScope : null, includePayments]})
    });

    return scopes;

};

bookings.getEventBookings = async function (req, res) {
    //need to loop over a users roles and assemble the data they are allowed to see
    const event = await db.event.findOne({where: {id: {[Op.eq]: req.params.eventId}}});

    const flat = await getBookingsAndCombineScopes(req.user, event)

    res.json({bookings: flat});

};

bookings.getBooking = (req, res) => {
    db.booking.findOne({where: {id: req.params.bookingId}, include: [{model: db.participant}, {model: db.event}, {model: db.payment}]})
    .then(booking => {
        let data = {};
        data.bookings = [booking];
        res.json(data);
    });
};

bookings.createBooking = (req, res) => {

    let newBooking = req.body;
    newBooking.guestUUID = req.cookies.guestUUID;
    newBooking.userId = newBooking.userId || req.user.id;
    newBooking.maxParticipants = newBooking.participants.length;
    newBooking.participants.forEach(p => {
        delete p.internalExtra
    });

    db.booking.create(newBooking, {
        include: [{
            association: 'participants'
        }]
    }).then((booking) => {
        return db.booking.findOne({where: {id: booking.id}, include: [{model: db.participant}, {model: db.event}]})
    }).then(booking => {
        log.log("debug", "Created new booking id %s for %s", booking.id, booking.userName);
        let data = {};
        data.bookings = [booking];
        res.json(data);

        //send an e-mail
        const fees = feeFactory(booking.event);
        const emailData = booking.get({plain: true});
        emailData.editURL = config.BASE_PATH + '/' + (emailData.userId === 1 ? "guestUUID/" + emailData.eventId + "/" + emailData.guestUUID : "event/" + emailData.eventId + "/book");
        emailData.user = req.user;
        email.single(booking.userEmail, 'confirmation', emailData);
        email.toManagers('managerBookingCreated', emailData);

        return null;//Don't want the request to wait on e-amil promise
    }).catch(e => {
        console.log(e);
        res.status(500).end();
    });
};

/*
bookings.editBooking = (req, res) => {
    Booking.findOne({ where: { id: req.body.id } })
        .then(booking => {
            booking.userName = req.body.user.name;
            booking.userEmail = req.body.user.email;
            booking.userContact = req.body.user.phone;
            booking.paymentType = req.body.paymentType;
            booking.emergencyName = req.body.emergencyName;
            booking.emergencyPhone = req.body.emergencyPhone;
            booking.note = req.body.note;
            Participant.destroy({ where: { bookingId: req.body.id } })//delete previous partiticpants
                .then(() => Promise.all(
                    req.body.participants.map(p => {
                        p.bookingId = req.body.id;
                        return Participant.upsert(p); //insert new ones
                    })))
                .then(() => booking.save())
                .then(() => Booking.findOne({ where: { id: req.body.id }, include: [{ model: Participant }] }))
                .then((booking) => {
                    let data = {};
                    data[booking.id] = booking;
                    res.json(data);
                });
        });
}
*/

bookings.editBooking = (req, res) => {

    delete req.body.internalExtra; //delete the internal extra so user can't update.
    delete req.body.payments
    req.body.participants.forEach(p => {
        delete p.internalExtra
    });
    db.booking.findOne({where: {id: req.body.id}})
    .then(booking => {
        req.body.maxParticipants = Math.max(booking.maxParticipants || 0, req.body.participants.length);
        return booking.update(req.body)//this ignores partitipants!
    })
    .then(booking => db.booking.findOne({where: {id: booking.id}, include: [{model: db.participant}]}))
    .then(booking => updateAssociation(booking, 'participants', db.participant, req.body.participants))
    .then(() => db.booking.findOne({where: {id: req.body.id}, include: [{model: db.participant}, {model: db.payment}, {model:db.event}]}))
    .then(booking => {
        log.log("debug", "User %s Editing Booking id %s", req.user.userName, booking.id);
        let data = {};
        data.bookings = [booking];
        res.json(data);

        if(req.user.id === booking.userId) {
            const fees = feeFactory(booking.event);
            const emailData = booking.get({plain: true});
            emailData.editURL = config.BASE_PATH + '/' + (emailData.userId === 1 ? "guestUUID/" + emailData.eventId + "/" + emailData.guestUUID : "event/" + emailData.eventId + "/book");
            emailData.user = req.user;
            email.single(booking.userEmail, 'updated', emailData);
            email.toManagers('managerBookingUpdated', emailData);
        }
    }).catch(e => {
        log.error({message: "Error editing booking id error is {e} id was {id}",
        e: e,
        id: req.body.id})
    });
};

bookings.togglePaid = (req, res) => {
    db.booking.findOne({where: {id: req.body.id}, include: [{model: db.participant}]})
    .then(booking => {
        booking.paid = !booking.paid;
        return booking.save()
    })
    .then((booking) => {
        let data = {};
        data.bookings = [booking];
        res.json(data);
    });
};

bookings.deleteBooking = (req, res) => {
    db.booking.findOne({where: {id: req.body.id},include: [{model:db.event}, {model: db.participant}]})
    .then(booking => {
        const emailData = booking.get({plain: true});
        emailData.user = req.user;
        //email.single(booking.userEmail, 'updated', emailData);
        email.toManagers('managerBookingDeleted', emailData);
        return booking.destroy()
    })
    .then(() => db.booking.findAll({
                                       where:
                                           {
                                               $or:
                                                   [{guestUUID: req.cookies.guestUUID},
                                                       {
                                                           $and:
                                                               [{userId: req.user.id},
                                                                   {userId: {$not: 1}}
                                                               ]
                                                       }]
                                           }, include: [{model: db.participant}]
                                   }))
    .then(bookings => {
        let data = {bookings};
        res.json(data);
    });
};

bookings.assignVillage = async function (req, res) {
    const booking = await db.booking.findOne({where: {id: req.body.bookingId}, include: [{model: db.participant}]});
    booking.villageId = req.body.villageId;
    await booking.save();
    const data = {};
    data.bookings = [booking];
    res.json(data);
};

bookings.addPayment = async function (req, res, next) {

    await db.payment.create(req.body);

    const booking = await db.booking.findOne({
                                                 where:   {id: {[Op.eq]: req.body.bookingId}},
                                                 include: [{model: db.event}]
                                             }); //get the booking, but we can't send this as dangerous scope.

    const bookings = await getBookingAndCombineScopes(req.user, booking)

    res.json({bookings: bookings});
};

bookings.deletePayment = async function (req, res, next) {

    const payment = await db.payment.findOne({where: {id: {[Op.eq]: req.body.id}}});

    const booking = await db.booking.findOne({
                                                 where:   {id: {[Op.eq]: payment.bookingId}},
                                                 include: [{model: db.event}]
                                             }); //get the booking, but we can't send this as dangerous scope.

    await payment.destroy();

    const bookings = await getBookingAndCombineScopes(req.user, booking)

    res.json({bookings: bookings});
};

bookings.approveMembership = async function (req, res, next) {

    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, member: true};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.unapproveMembership = async function (req, res, next) {
    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, member: false};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.approveDBS = async function (req, res, next) {

    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, dbs: true};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

bookings.unapproveDBS = async function (req, res, next) {
    const participant = await db.participant.findOne({where: {id: {[Op.eq]: req.body.id}}});
    const booking = await db.booking.findOne({where: {id: {[Op.eq]: participant.bookingId}}, include: [{model: db.event}]});

    participant.internalExtra = {... participant.internalExtra, dbs: false};

    await participant.save();

    const bookings = await getBookingAndCombineScopes(req.user, booking);

    res.json({bookings: bookings});
};

/**
 *  Test function to update a paricipants updatedAt, disabled in production enviroments.
 *
 */

bookings.updateParticipantDate = async function (req, res) {

    const results = await db.sequelize.query('UPDATE participants SET "updatedAt" = $date WHERE id = $id',
                                             {
                                                 bind: {
                                                     id:   req.params.participantId,
                                                     date: moment(req.params.date).format('YYYY-MM-DD HH:mm:ss.SSS')
                                                 },
                                                 type: db.sequelize.QueryTypes.UPDATE
                                             });

    const paricipant = await db.participant.findOne({where: {id: req.params.participantId}});

    res.json(paricipant);

};

const getBookingAndCombineScopes = async function (user, booking) {

    const scopes = getUserScopes(user, booking.event);
    const results = await Promise.all(scopes.map(s => db.booking.scope(s).findOne({where: {id: booking.id}})));

    const obj = results.filter(r => r).reduce((a, c) => {

        _.merge(a, c.get({plain: true}))
        return a

    }, {});

    return [obj]
}

const getBookingsAndCombineScopes = async function (user, event) {
    const scopes = getUserScopes(user, event);
    console.log(`getting details for ${scopes.length} scopes`)
    console.log(scopes)
    const results = await Promise.all(scopes.map(s => db.booking.scope(s).findAll()));

    const obj = results.filter(r => r).reduce((a, c) => {

        c.forEach(b => {

            a[b.id] = a[b.id] || {}

            _.merge(a[b.id], b.get({plain: true}))

        });

        return a

    }, {});

    const flat = []

    for (let key in obj) flat.push(obj[key])

    return flat
}

module.exports = wrapper(bookings);

<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');

const Map = require(path.join(__dirname, 'map.js'));
const Record = require(path.join(__dirname, 'record.js'));

function load_uncharted(buffer, def) {
    return buffer.slice(def.offset, def.offset + def.record_length);
}

function load_records(buffer, def) {
    const arr = [];
    for (let n = 0; n < def.records; n++) {
        const offset = def.offset + (def.record_length * n);
        arr.push(new Record(buffer.slice(offset, offset + def.record_length), def));
    }
    return arr;
}

function serialize_section(sec) {
    let buffer = Buffer.alloc(0);
    sec.forEach(
        (e) => {
            buffer = Buffer.concat([ buffer, e.serialize() ]);
        }
    );
    return buffer;
}

class Codeplug {

    constructor (buffer) {

        if (!Buffer.isBuffer(buffer)) {
            buffer = fs.readFileSync(path.join(__dirname, 'default1.rdt'));
        }

        this._properties = {};

        this._properties.uncharted_1 = load_uncharted(buffer, Map.uncharted_1);
        this._properties.settings = new Record(
            buffer.slice(
                Map.settings.offset,
                Map.settings.offset + Map.settings.record_length
            ),
            Map.settings
        );
        this._properties.uncharted_2 = load_uncharted(buffer, Map.uncharted_2);
        this._properties.menu_items = new Record(
            buffer.slice(
                Map.menu_items.offset,
                Map.menu_items.offset + Map.menu_items.record_length
            ),
            Map.menu_items
        );
        this._properties.uncharted_3 = load_uncharted(buffer, Map.uncharted_3);
        this._properties.text_messages = load_records(buffer, Map.text_messages);
        this._properties.uncharted_4 = load_uncharted(buffer, Map.uncharted_3);
        this._properties.contacts = load_records(buffer, Map.contacts);
        this._properties.receive_groups = load_records(buffer, Map.receive_groups);
        this._properties.zones = load_records(buffer, Map.zones);
        this._properties.scan_lists = load_records(buffer, Map.scan_lists);
        this._properties.uncharted_5 = load_uncharted(buffer, Map.uncharted_4);
        this._properties.channels = load_records(buffer, Map.channels);
        this._properties.uncharted_6 = load_uncharted(buffer, Map.uncharted_5);

        Object.keys(this._properties).forEach(
            (e) => {
                Object.defineProperty(
                    this, e, { get : () => this._properties[e] }
                );
            }
        );

    }

    serialize() {
        let buffer = Buffer.alloc(0);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_1 ]);
        buffer = Buffer.concat([ buffer, this._properties.settings.serialize() ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_2 ]);
        buffer = Buffer.concat([ buffer, this._properties.menu_items.serialize() ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_3 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.text_messages) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_4 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.contacts) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.receive_groups) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.zones) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.scan_lists) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_5 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.channels) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_6 ]);
        return buffer;
    }

}

module.exports = Codeplug;
<commit_msg>Corrected property names<commit_after>'use strict';

const fs = require('fs');
const path = require('path');

const Map = require(path.join(__dirname, 'map.js'));
const Record = require(path.join(__dirname, 'record.js'));

function load_uncharted(buffer, def) {
    return buffer.slice(def.offset, def.offset + def.record_length);
}

function load_records(buffer, def) {
    const arr = [];
    for (let n = 0; n < def.records; n++) {
        const offset = def.offset + (def.record_length * n);
        arr.push(new Record(buffer.slice(offset, offset + def.record_length), def));
    }
    return arr;
}

function serialize_section(sec) {
    let buffer = Buffer.alloc(0);
    sec.forEach(
        (e) => {
            buffer = Buffer.concat([ buffer, e.serialize() ]);
        }
    );
    return buffer;
}

class Codeplug {

    constructor (buffer) {

        if (!Buffer.isBuffer(buffer)) {
            buffer = fs.readFileSync(path.join(__dirname, 'default1.rdt'));
        }

        this._properties = {};

        this._properties.uncharted_1 = load_uncharted(buffer, Map.uncharted_1);
        this._properties.settings = new Record(
            buffer.slice(
                Map.settings.offset,
                Map.settings.offset + Map.settings.record_length
            ),
            Map.settings
        );
        this._properties.uncharted_2 = load_uncharted(buffer, Map.uncharted_2);
        this._properties.menu_items = new Record(
            buffer.slice(
                Map.menu_items.offset,
                Map.menu_items.offset + Map.menu_items.record_length
            ),
            Map.menu_items
        );
        this._properties.uncharted_3 = load_uncharted(buffer, Map.uncharted_3);
        this._properties.text_messages = load_records(buffer, Map.text_messages);
        this._properties.uncharted_4 = load_uncharted(buffer, Map.uncharted_4);
        this._properties.contacts = load_records(buffer, Map.contacts);
        this._properties.receive_groups = load_records(buffer, Map.receive_groups);
        this._properties.zones = load_records(buffer, Map.zones);
        this._properties.scan_lists = load_records(buffer, Map.scan_lists);
        this._properties.uncharted_5 = load_uncharted(buffer, Map.uncharted_5);
        this._properties.channels = load_records(buffer, Map.channels);
        this._properties.uncharted_6 = load_uncharted(buffer, Map.uncharted_6);

        Object.keys(this._properties).forEach(
            (e) => {
                Object.defineProperty(
                    this, e, { get : () => this._properties[e] }
                );
            }
        );

    }

    serialize() {
        let buffer = Buffer.alloc(0);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_1 ]);
        buffer = Buffer.concat([ buffer, this._properties.settings.serialize() ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_2 ]);
        buffer = Buffer.concat([ buffer, this._properties.menu_items.serialize() ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_3 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.text_messages) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_4 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.contacts) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.receive_groups) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.zones) ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.scan_lists) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_5 ]);
        buffer = Buffer.concat([ buffer, serialize_section(this._properties.channels) ]);
        buffer = Buffer.concat([ buffer, this._properties.uncharted_6 ]);
        return buffer;
    }

}

module.exports = Codeplug;
<|endoftext|>


<commit_before>import  * as THREE from '../node_modules/three/build/three.module.js'

// Settings
const maxLeafNodes = 10;
const maxMatchingTriangles = 0.5;

// Utilities
const getBufferGeometryVertexElem = (geo, tri, vert, elem) => {
    return geo.attributes.position.array[(geo.index ? geo.index.array[3 * tri + vert] : (3 * tri + vert)) * 3  + elem];
}

const getGeometryVertexElem = (geo, tri, vert, elem) => {
    return geo.faces[tri][abcFields[vert]][xyzFields[elem]];
}

const abcFields = ['a', 'b', 'c'];
const xyzFields = ['x', 'y', 'z'];
const getLongestEdgeIndex = bb => {
    let splitDimIdx = -1;
    let splitDist = -Infinity;
    xyzFields.forEach((d, i) => {
        const dist = bb.max[d] - bb.min[d];
        if (dist > splitDist) {
            splitDist = dist;
            splitDimIdx = i;
        }
    });
    return splitDimIdx;
}

// reusable vectors
const avgtemp = new THREE.Vector3();
const vectemp = new THREE.Vector3();
const centemp = new THREE.Vector3();

const getBounds = (tris, bounds, avg, geo, getValFunc) => {
    avg.set(0, 0, 0);

    for (let i = 0; i < tris.length; i ++) {
        const tri = tris[i];

        for (let v = 0; v < 3; v ++) {
            const x = getValFunc(geo, tri, v, 0);
            const y = getValFunc(geo, tri, v, 1);
            const z = getValFunc(geo, tri, v, 2);

            vectemp.x = x;
            vectemp.y = y;
            vectemp.z = z;
            bounds.expandByPoint(vectemp);

            avg.x += x;
            avg.y += y;
            avg.z += z;
        }
    }

    avg.x /= tris.length * 3;
    avg.y /= tris.length * 3;
    avg.z /= tris.length * 3;
}

const getSphere = (tris, sphere, geo, getValFunc) => {
    const center = sphere.center;
    let maxRadiusSq = 0;

    for (let i = 0; i < tris.length; i ++) {
        const tri = tris[i];

        for (let v = 0; v < 3; v ++) {
            const x = getValFunc(geo, tri, v, 0);
            const y = getValFunc(geo, tri, v, 1);
            const z = getValFunc(geo, tri, v, 2);

            vectemp.x = x;
            vectemp.y = y;
            vectemp.z = z;

            maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(vectemp));
        }
    }

    sphere.radius = Math.sqrt(maxRadiusSq);
}

// Classes
class TriangleBoundsNode {
    constructor() {
        this.boundingBox = new THREE.Box3();
        this.boundingSphere = new THREE.Sphere();
        this.children = [];
        this.tris = [];
    }
}

class TriangleBoundsTree {
    constructor(geo) {
        if (geo.isBufferGeometry || geo.isGeometry) {
            this._root = this._buildTree(geo);
        } else {
            throw new Error('Object is not Geometry or BufferGeometry');
        }
    }

    /* Public API */
    collectCandidates(origray) {
        const candidates = [];

        let sptime = 0;
        let bxtime = 0;
        let dttime = 0;

        const recurse = (node, ray) => {

            let st = window.performance.now();
            const is = ray.intersectsSphere(node.boundingSphere)
            if (!is) return
            sptime += window.performance.now() - st;

            st = window.performance.now()

            const ib = ray.intersectsBox(node.boundingBox);
            if (!ib) return
            bxtime += window.performance.now() - st;
            // if (!is || !ray.intersectsBox(node.boundingBox)) return;

            if (node.children.length) {
                node.children.forEach(c => recurse(c, ray))
            } else {
                st = window.performance.now()
                candidates.push(...node.tris)
                dttime += window.performance.now() - st;
            }
        }
        recurse(this._root, origray);

        return candidates;
    }

    /* Private Functions */
    _buildTree(geo) {
        const vertexElem = geo.isBufferGeometry ? getBufferGeometryVertexElem : getGeometryVertexElem;

        // a list of every available triangle index
        const origTris =
            geo.isBufferGeometry ?
                new Array(geo.index ? (geo.index.count / 3) : (pos.length / 9)) :
                Array(geo.faces.length);

        for (let i = 0; i < origTris.length; i ++) origTris[i] = i;

        // use a queue to run the node creation functions
        // because otherwise we run the risk of a stackoverflow
        // In the case of buffer geometry it also seems to be
        // faster than recursing
        const queue = [];
        const createNode = tris => {
            const node = new TriangleBoundsNode();

            // get the bounds of the triangles
            getBounds(tris, node.boundingBox, avgtemp, geo, vertexElem);
            node.boundingBox.getCenter(node.boundingSphere.center);
            getSphere(tris, node.boundingSphere, geo, vertexElem);

            if (tris.length <= maxLeafNodes) {
                node.tris = tris;
                return node;
            }

            // decide which axis to split on (longest edge)
            const splitDimIdx = getLongestEdgeIndex(node.boundingBox);
            const splitDimStr = xyzFields[splitDimIdx];

            const left = [], right = [], shared = [];
            for (let i = 0; i < tris.length; i ++) {
                const tri = tris[i];

                let inLeft = false;
                let inRight = false;

                for (let v = 0; v < 3; v ++) {
                    const val = vertexElem(geo, tri, v, splitDimIdx);

                    inLeft = inLeft || val >= avgtemp[splitDimStr];
                    inRight = inRight || val <= avgtemp[splitDimStr];
                }

                if (inLeft && inRight) shared.push(tri);
                if (inLeft) left.push(tri);
                if (inRight && !inLeft) right.push(tri);
            }

            if (shared.length / tris.length >= maxMatchingTriangles) {
                node.tris = tris;
            } else {
                if (left.length)    queue.push(() => node.children.push(createNode(left)));
                if (right.length)   queue.push(() => node.children.push(createNode(right)));
            }

            return node;
        }

        const n = createNode(origTris);
        while (queue.length) queue.pop()();
        return n;
    }
}

export default TriangleBoundsTree;
<commit_msg>Cleanup<commit_after>import  * as THREE from '../node_modules/three/build/three.module.js'

// Settings
const maxLeafNodes = 10;
const maxMatchingTriangles = 0.5;

// Utilities
const getBufferGeometryVertexElem = (geo, tri, vert, elem) => {
    return geo.attributes.position.array[(geo.index ? geo.index.array[3 * tri + vert] : (3 * tri + vert)) * 3  + elem];
}

const getGeometryVertexElem = (geo, tri, vert, elem) => {
    return geo.faces[tri][abcFields[vert]][xyzFields[elem]];
}

const abcFields = ['a', 'b', 'c'];
const xyzFields = ['x', 'y', 'z'];
const getLongestEdgeIndex = bb => {
    let splitDimIdx = -1;
    let splitDist = -Infinity;
    xyzFields.forEach((d, i) => {
        const dist = bb.max[d] - bb.min[d];
        if (dist > splitDist) {
            splitDist = dist;
            splitDimIdx = i;
        }
    });
    return splitDimIdx;
}

// reusable vectors
const avgtemp = new THREE.Vector3();
const vectemp = new THREE.Vector3();
const centemp = new THREE.Vector3();

const getBounds = (tris, bounds, avg, geo, getValFunc) => {
    avg.set(0, 0, 0);

    for (let i = 0; i < tris.length; i ++) {
        const tri = tris[i];

        for (let v = 0; v < 3; v ++) {
            const x = getValFunc(geo, tri, v, 0);
            const y = getValFunc(geo, tri, v, 1);
            const z = getValFunc(geo, tri, v, 2);

            vectemp.x = x;
            vectemp.y = y;
            vectemp.z = z;
            bounds.expandByPoint(vectemp);

            avg.x += x;
            avg.y += y;
            avg.z += z;
        }
    }

    avg.x /= tris.length * 3;
    avg.y /= tris.length * 3;
    avg.z /= tris.length * 3;
}

const getSphere = (tris, sphere, geo, getValFunc) => {
    const center = sphere.center;
    let maxRadiusSq = 0;

    for (let i = 0; i < tris.length; i ++) {
        const tri = tris[i];

        for (let v = 0; v < 3; v ++) {
            const x = getValFunc(geo, tri, v, 0);
            const y = getValFunc(geo, tri, v, 1);
            const z = getValFunc(geo, tri, v, 2);

            vectemp.x = x;
            vectemp.y = y;
            vectemp.z = z;

            maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(vectemp));
        }
    }

    sphere.radius = Math.sqrt(maxRadiusSq);
}

// Classes
class TriangleBoundsNode {
    constructor() {
        this.boundingBox = new THREE.Box3();
        this.boundingSphere = new THREE.Sphere();
        this.children = [];
        this.tris = [];
    }
}

class TriangleBoundsTree {
    constructor(geo) {
        if (geo.isBufferGeometry || geo.isGeometry) {
            this._root = this._buildTree(geo);
        } else {
            throw new Error('Object is not Geometry or BufferGeometry');
        }
    }

    /* Public API */
    collectCandidates(origray) {
        const candidates = [];
        const recurse = (node, ray) => {

            if (!ray.intersectsSphere(node.boundingSphere) || !ray.intersectsBox(node.boundingBox)) return;

            if (node.children.length) {
                node.children.forEach(c => recurse(c, ray))
            } else {
                st = window.performance.now()
                candidates.push(...node.tris)
                dttime += window.performance.now() - st;
            }
        }
        recurse(this._root, origray);

        return candidates;
    }

    /* Private Functions */
    _buildTree(geo) {
        const vertexElem = geo.isBufferGeometry ? getBufferGeometryVertexElem : getGeometryVertexElem;

        // a list of every available triangle index
        const origTris =
            geo.isBufferGeometry ?
                new Array(geo.index ? (geo.index.count / 3) : (pos.length / 9)) :
                Array(geo.faces.length);

        for (let i = 0; i < origTris.length; i ++) origTris[i] = i;

        // use a queue to run the node creation functions
        // because otherwise we run the risk of a stackoverflow
        // In the case of buffer geometry it also seems to be
        // faster than recursing
        const queue = [];
        const createNode = tris => {
            const node = new TriangleBoundsNode();

            // get the bounds of the triangles
            getBounds(tris, node.boundingBox, avgtemp, geo, vertexElem);
            node.boundingBox.getCenter(node.boundingSphere.center);
            getSphere(tris, node.boundingSphere, geo, vertexElem);

            if (tris.length <= maxLeafNodes) {
                node.tris = tris;
                return node;
            }

            // decide which axis to split on (longest edge)
            const splitDimIdx = getLongestEdgeIndex(node.boundingBox);
            const splitDimStr = xyzFields[splitDimIdx];

            const left = [], right = [];
            let sharedCount = 0;
            for (let i = 0; i < tris.length; i ++) {
                const tri = tris[i];

                let inLeft = false;
                let inRight = false;

                for (let v = 0; v < 3; v ++) {
                    const val = vertexElem(geo, tri, v, splitDimIdx);

                    inLeft = inLeft || val >= avgtemp[splitDimStr];
                    inRight = inRight || val <= avgtemp[splitDimStr];
                }

                if (inLeft) left.push(tri);
                if (inRight) right.push(tri);
                if (inLeft && inRight) sharedCount ++;
            }

            if (sharedCount / tris.length >= maxMatchingTriangles) {
                node.tris = tris;
            } else {
                if (left.length)    queue.push(() => node.children.push(createNode(left)));
                if (right.length)   queue.push(() => node.children.push(createNode(right)));
            }

            return node;
        }

        const n = createNode(origTris);
        while (queue.length) queue.pop()();
        return n;
    }
}

export default TriangleBoundsTree;
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import merge from 'webpack-merge';

import path from 'path';

import commonCfg from './webpack.common';

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const backendPort = process.env.BACKEND_PORT || 3000;
const devServerPort = process.env.PORT || 9000;
const devServerHost = process.env.HOST || '0.0.0.0';

export default merge(commonCfg, {
    mode: 'development',
    devtool: 'source-map',
    watch: true,
    module: {
        exprContextCritical: false,
        rules: [
            {
                test: /\.css$/,
                use: ['style', 'css']
            },
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader, // style-loader does not work with styles in IgniteModules
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true,
                            includePaths: [ path.join(__dirname, '../') ]
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({filename: 'assets/css/[name].css'})
    ],
    devServer: {
        headers: {
            'Content-Security-Policy': `script-src 'self' 'unsafe-inline' 'unsafe-eval' data: http: https:;`
        },
        compress: true,
        historyApiFallback: true,
        disableHostCheck: true,
        contentBase: path.resolve('build'),
        // hot: true,
        inline: true,
        proxy: {
            '/socket.io': {
                target: `http://localhost:${backendPort}`,
                ws: true
            },
            '/agents': {
                target: `http://localhost:${backendPort}`,
                ws: true
            },
            '/api/*': {
                target: `http://localhost:${backendPort}`
            }
        },
        watchOptions: {
            aggregateTimeout: 1000,
            poll: 2000
        },
        stats: 'errors-only',
        host: devServerHost,
        port: devServerPort
    }
});
<commit_msg>IGNITE-8962 Web Console: Fixed webpack configuration for DEV mode.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import merge from 'webpack-merge';

import path from 'path';

import commonCfg from './webpack.common';

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const backendPort = process.env.BACKEND_PORT || 3000;
const devServerPort = process.env.PORT || 9000;
const devServerHost = process.env.HOST || '0.0.0.0';

export default merge(commonCfg, {
    mode: 'development',
    devtool: 'source-map',
    watch: true,
    module: {
        exprContextCritical: false,
        rules: [
            {
                test: /\.css$/,
                use: ['style', 'css']
            },
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader, // style-loader does not work with styles in IgniteModules
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true,
                            includePaths: [ path.join(__dirname, '../') ]
                        }
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({filename: 'assets/css/[name].css'})
    ],
    devServer: {
        compress: true,
        historyApiFallback: true,
        disableHostCheck: true,
        contentBase: path.resolve('build'),
        inline: true,
        proxy: {
            '/socket.io': {
                target: `http://localhost:${backendPort}`,
                ws: true
            },
            '/agents': {
                target: `http://localhost:${backendPort}`,
                ws: true
            },
            '/api/*': {
                target: `http://localhost:${backendPort}`
            }
        },
        watchOptions: {
            aggregateTimeout: 1000,
            poll: 2000
        },
        stats: 'errors-only',
        host: devServerHost,
        port: devServerPort
    }
});
<|endoftext|>


<commit_before>import { Minutes } from '/imports/minutes';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { ReactiveVar } from 'meteor/reactive-var';
import { $ } from 'meteor/jquery';
import { MeetingSeries } from '/imports/meetingseries';
import { Topic } from '/imports/topic';
import { ConfirmationDialogFactory } from '../../helpers/confirmationDialogFactory';
import { FlashMessage } from '../../helpers/flashMessage';
import { TopicInfoItemListContext } from './topicInfoItemList';
import {LabelResolver} from '../../../imports/services/labelResolver';
import {ResponsibleResolver} from '../../../imports/services/responsibleResolver';
import {labelSetFontColor} from './helpers/label-set-font-color';
import { handleError } from '../../helpers/handleError';
import {detectTypeAndCreateItem} from './helpers/create-item';
import {resizeTextarea} from './helpers/resize-textarea';

let _minutesId;

let onError = (error) => {
    (new FlashMessage('Error', error.reason)).show();
};

const INITIAL_ITEMS_LIMIT = 4;

const isFeatureShowItemInputFieldOnDemandEnabled = () => {
    return !(Meteor.settings && Meteor.settings.public && Meteor.settings.public.isEnd2EndTest);
};

Template.topicElement.onCreated(function () {
    let tmplData = Template.instance().data;
    _minutesId = tmplData.minutesID;

    this.isItemsLimited = new ReactiveVar(tmplData.topic.infoItems.length > INITIAL_ITEMS_LIMIT);
    this.isCollapsed = new ReactiveVar(false);
});

Template.topicElement.helpers({
    hideAddItemInputField() {
        return isFeatureShowItemInputFieldOnDemandEnabled();
    },

    getLabels: function() {
        let tmplData = Template.instance().data;
        return LabelResolver.resolveLabels(this.topic.labels, tmplData.parentMeetingSeriesId).map(labelSetFontColor);
    },

    checkedState: function () {
        if (this.topic.isOpen) {
            return '';
        } else {
            return {checked: 'checked'};
        }
    },

    disabledState: function () {
        if ((this.isEditable) && (!this.topic.isSkipped)) {
            return '';
        } else {
            return {disabled: 'disabled'};
        }
    },

    // determine if this topic shall be rendered collapsed
    isCollapsed() {
        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        return collapseState ? collapseState[this.topic._id] : false;
    },

    showRecurringIcon() {
        return (this.isEditable || this.topic.isRecurring);
    },

    responsiblesHelper() {
        try {
            const responsible = ResponsibleResolver.resolveAndformatResponsiblesString(this.topic.responsibles);
            return (responsible) ? `(${responsible})` : '';
        } catch (e) {
            // intentionally left blank.
            // on deletion of a topic blaze once calls this method on the just deleted topic
            // we handle this gracefully with this empty exception handler
        }
        return '';
    },

    getData() {
        const data = Template.instance().data;
        const parentElement = (data.minutesID) ? data.minutesID : data.parentMeetingSeriesId;
        return TopicInfoItemListContext.createContextForItemsOfOneTopic(
            data.topic.infoItems,
            !data.isEditable,
            parentElement,
            data.topic._id
        );
    },

    classForEdit() {
        return this.isEditable ? 'btnEditTopic' : '';
    },

    classForSkippedTopics() {
        return this.topic.isSkipped ? 'strikethrough' : '';
    },

    cursorForEdit() {
        return this.isEditable ? 'pointer' : '';
    },

    showMenu() {
        return ((this.isEditable) || // Context: Current non-finalized Minute
            (!this.minutesID && !this.topic.isOpen && new MeetingSeries(this.parentMeetingSeriesId).isCurrentUserModerator())); // Context: Closed Topic within MeetingSeries, user is moderator;
    }
});

const editTopicEventHandler = (evt, context, manipulateTopic) => {
    evt.preventDefault();
    if (!context.minutesID || !context.isEditable) {
        return;
    }
    const aTopic = new Topic(context.minutesID, context.topic._id);
    manipulateTopic(aTopic);
};

const openAddItemDialog = (itemType, topicId) => {
    Session.set('topicInfoItemEditTopicId', topicId);
    Session.set('topicInfoItemType', itemType);
};
const showHideItemInput = (tmpl, show = true, then = () => {}) => {
    if (!isFeatureShowItemInputFieldOnDemandEnabled()) {
        return;
    }

    const addItemEl = tmpl.$('.addItemForm');
    if (show) {
        addItemEl.show(250);
        Meteor.setTimeout(() => {
            resizeTextarea(tmpl.$('.add-item-field'));
            then();
            Meteor.setTimeout(() => {
                tmpl.$('.add-item-field').focus();
            },50);
        }, 300);
    } else {
        addItemEl.hide(250);
    }
};

let savingNewItem = false;

Template.topicElement.events({

    'focus .topic-element'(evt, tmpl) {
        if (tmpl.$('.topic-element').hasClass('focus')) {   // guard against multiple nested calls
            return;
        }
        tmpl.$('.topic-element').addClass('focus');
        showHideItemInput(tmpl);
    },

    'blur .topic-element'(evt, tmpl) {
        if (! tmpl.$('.topic-element').hasClass('focus')) { // guard against multiple nested calls
            return;
        }
        if (savingNewItem) {
            savingNewItem = false;
            return;
        }
        const nextElement = evt.relatedTarget;
        const topicElement = tmpl.find('.topic-element');
        if (!nextElement || !topicElement.contains(nextElement)) {
            tmpl.$('.topic-element').removeClass('focus');
            // Meteor.setTimeout(() => { showHideItemInput(tmpl, false); }, 500);
            showHideItemInput(tmpl, false);
        }
    },

    'click #btnDelTopic'(evt) {
        evt.preventDefault();

        if (!this.minutesID) {
            return;
        }
        console.log('Delete topics: '+this.topic._id+' from minutes '+this.minutesID);

        let aMin = new Minutes(this.minutesID);

        let topic = new Topic(this.minutesID, this.topic);
        const deleteAllowed = topic.isDeleteAllowed();

        if (!topic.isClosedAndHasNoOpenAIs() || deleteAllowed) {
            ConfirmationDialogFactory.makeWarningDialogWithTemplate(
                () => {
                    if (deleteAllowed) {
                        aMin.removeTopic(this.topic._id).catch(onError);
                    } else {
                        topic.closeTopicAndAllOpenActionItems().catch(onError);
                    }
                },
                deleteAllowed ? 'Confirm delete' : 'Close topic?',
                'confirmDeleteTopic',
                {
                    deleteAllowed: topic.isDeleteAllowed(),
                    hasOpenActionItems: topic.hasOpenActionItem(),
                    subject: topic.getSubject()
                },
                deleteAllowed ? 'Delete' : 'Close topic and actions'
            ).show();
        } else {
            ConfirmationDialogFactory.makeInfoDialog(
                'Cannot delete topic',
                'It is not possible to delete this topic because it was created in a previous minutes. ' +
                'The selected topic is already closed and has no open action items, so it won\'t be copied to the ' +
                'following minutes'
            ).show();
        }
    },

    'click .btnToggleState'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleState().catch(onError);
        });
    },

    'click .js-toggle-recurring'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleRecurring();
            aTopic.save().catch(onError);
        });
    },

    'click .js-toggle-skipped'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleSkip();
            aTopic.save().catch(onError);
        });
    },

    'click #btnEditTopic'(evt) {
        evt.preventDefault();
        if (!this.minutesID || getSelection().toString()) { // don't fire while selection is ongoing
            return;
        }
        Session.set('topicEditTopicId', this.topic._id);
        $('#dlgAddTopic').modal('show');
    },

    'click .addTopicInfoItem'(evt) {
        evt.preventDefault();
        // will be called before the modal dialog is shown
        openAddItemDialog('infoItem', this.topic._id);
    },

    'click .addTopicActionItem'(evt) {
        evt.preventDefault();
        // will be called before the modal dialog is shown
        openAddItemDialog('actionItem', this.topic._id);
    },

    'blur .addItemForm' (evt, tmpl) {
        if (!tmpl.data.isEditable) {
            throw new Meteor.Error('illegal-state', 'Tried to call an illegal event in read-only mode');
        }

        const inputText = tmpl.find('.add-item-field').value;

        if (inputText === '') {
            return;
        }

        savingNewItem = true;
        const splitIndex = inputText.indexOf('\n');
        const subject = (splitIndex === -1) ? inputText : inputText.substring(0, splitIndex);
        const detail = (splitIndex === -1) ? '' : inputText.substring(splitIndex + 1).trim();

        const itemDoc = {
            subject: subject,
            responsibles: [],
            createdInMinute: this.minutesID
        };

        const topic = new Topic(this.minutesID, this.topic);
        const minutes = new Minutes(this.minutesID);
        const newItem = detectTypeAndCreateItem(itemDoc, topic, this.minutesID, minutes.parentMeetingSeries());
        if (detail) {
            newItem.addDetails(this.minutesID, detail);
        }
        newItem.saveAtBottom().catch(error => {
            tmpl.find('.add-item-field').value = inputText; // set desired value again!
            handleError(error);
        });
        tmpl.find('.add-item-field').value = '';
        tmpl.$('.add-item-field').focus();
        resizeTextarea(tmpl.$('.add-item-field'));

        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        if (!collapseState) {
            collapseState = {};
        }
        collapseState[this.topic._id] = false;
        Session.set('minutesedit.collapsetopics.'+_minutesId, collapseState);
    },

    'keydown .addItemForm' (evt, tmpl) {
        const inputEl = tmpl.$('.add-item-field');
        if (evt.which === 13/*enter*/ && ( evt.ctrlKey || evt.metaKey)) {
            evt.preventDefault();
            inputEl.blur();
        }

        resizeTextarea(inputEl);
    },

    'keydown #btnTopicExpandCollapse'(evt) {
        evt.preventDefault();
        // since we do not have a link-href the link will not be clicked when hitting enter by default...
        if (evt.which === 13/*enter*/) {
            evt.currentTarget.click();
        }
    },

    'click #btnTopicExpandCollapse'(evt) {
        console.log('btnTopicExpandCollapse()'+this.topic._id);
        evt.preventDefault();
        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        if (!collapseState) {
            collapseState = {};
        }
        collapseState[this.topic._id] = ! collapseState[this.topic._id];
        Session.set('minutesedit.collapsetopics.'+_minutesId, collapseState);
    },

    'click #btnReopenTopic'(evt) {
        evt.preventDefault();
        let reopenTopic = () => {
            Meteor.call('workflow.reopenTopicFromMeetingSeries', this.parentMeetingSeriesId, this.topic._id);
        };
        ConfirmationDialogFactory.makeSuccessDialogWithTemplate(
            reopenTopic,
            'Re-open Topic',
            'confirmReOpenTopic',
            {
                topicSubject: Template.instance().data.topic.subject
            },
            'Re-open'
        ).show();
    }
});<commit_msg>Fix: Only focus "quick item" <textarea> if parent topic fits inside viewport. Fixes #419<commit_after>import { Minutes } from '/imports/minutes';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { ReactiveVar } from 'meteor/reactive-var';
import { $ } from 'meteor/jquery';
import { MeetingSeries } from '/imports/meetingseries';
import { Topic } from '/imports/topic';
import { ConfirmationDialogFactory } from '../../helpers/confirmationDialogFactory';
import { FlashMessage } from '../../helpers/flashMessage';
import { TopicInfoItemListContext } from './topicInfoItemList';
import {LabelResolver} from '../../../imports/services/labelResolver';
import {ResponsibleResolver} from '../../../imports/services/responsibleResolver';
import {labelSetFontColor} from './helpers/label-set-font-color';
import { handleError } from '../../helpers/handleError';
import {detectTypeAndCreateItem} from './helpers/create-item';
import {resizeTextarea} from './helpers/resize-textarea';

let _minutesId;

let onError = (error) => {
    (new FlashMessage('Error', error.reason)).show();
};

const INITIAL_ITEMS_LIMIT = 4;

const isFeatureShowItemInputFieldOnDemandEnabled = () => {
    return !(Meteor.settings && Meteor.settings.public && Meteor.settings.public.isEnd2EndTest);
};

Template.topicElement.onCreated(function () {
    let tmplData = Template.instance().data;
    _minutesId = tmplData.minutesID;

    this.isItemsLimited = new ReactiveVar(tmplData.topic.infoItems.length > INITIAL_ITEMS_LIMIT);
    this.isCollapsed = new ReactiveVar(false);
});

Template.topicElement.helpers({
    hideAddItemInputField() {
        return isFeatureShowItemInputFieldOnDemandEnabled();
    },

    getLabels: function() {
        let tmplData = Template.instance().data;
        return LabelResolver.resolveLabels(this.topic.labels, tmplData.parentMeetingSeriesId).map(labelSetFontColor);
    },

    checkedState: function () {
        if (this.topic.isOpen) {
            return '';
        } else {
            return {checked: 'checked'};
        }
    },

    disabledState: function () {
        if ((this.isEditable) && (!this.topic.isSkipped)) {
            return '';
        } else {
            return {disabled: 'disabled'};
        }
    },

    // determine if this topic shall be rendered collapsed
    isCollapsed() {
        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        return collapseState ? collapseState[this.topic._id] : false;
    },

    showRecurringIcon() {
        return (this.isEditable || this.topic.isRecurring);
    },

    responsiblesHelper() {
        try {
            const responsible = ResponsibleResolver.resolveAndformatResponsiblesString(this.topic.responsibles);
            return (responsible) ? `(${responsible})` : '';
        } catch (e) {
            // intentionally left blank.
            // on deletion of a topic blaze once calls this method on the just deleted topic
            // we handle this gracefully with this empty exception handler
        }
        return '';
    },

    getData() {
        const data = Template.instance().data;
        const parentElement = (data.minutesID) ? data.minutesID : data.parentMeetingSeriesId;
        return TopicInfoItemListContext.createContextForItemsOfOneTopic(
            data.topic.infoItems,
            !data.isEditable,
            parentElement,
            data.topic._id
        );
    },

    classForEdit() {
        return this.isEditable ? 'btnEditTopic' : '';
    },

    classForSkippedTopics() {
        return this.topic.isSkipped ? 'strikethrough' : '';
    },

    cursorForEdit() {
        return this.isEditable ? 'pointer' : '';
    },

    showMenu() {
        return ((this.isEditable) || // Context: Current non-finalized Minute
            (!this.minutesID && !this.topic.isOpen && new MeetingSeries(this.parentMeetingSeriesId).isCurrentUserModerator())); // Context: Closed Topic within MeetingSeries, user is moderator;
    }
});

const editTopicEventHandler = (evt, context, manipulateTopic) => {
    evt.preventDefault();
    if (!context.minutesID || !context.isEditable) {
        return;
    }
    const aTopic = new Topic(context.minutesID, context.topic._id);
    manipulateTopic(aTopic);
};

const openAddItemDialog = (itemType, topicId) => {
    Session.set('topicInfoItemEditTopicId', topicId);
    Session.set('topicInfoItemType', itemType);
};
const showHideItemInput = (tmpl, show = true, then = () => {}) => {
    if (!isFeatureShowItemInputFieldOnDemandEnabled()) {
        return;
    }

    const addItemEl = tmpl.$('.addItemForm');
    const theItemTextarea = tmpl.$('.add-item-field');
    if (show) {
        addItemEl.show(250);
        Meteor.setTimeout(() => {
            resizeTextarea(theItemTextarea);
            then();

            Meteor.setTimeout(() => {
                // only focus, if parent topic element fits in view port
                if (tmpl.$('.topic-element').outerHeight() < window.innerHeight) {
                    theItemTextarea.focus();
                }
            },50);
        }, 300);
    } else {
        addItemEl.hide(250);
    }
};

let savingNewItem = false;

Template.topicElement.events({

    'focus .topic-element'(evt, tmpl) {
        if (tmpl.$('.topic-element').hasClass('focus')) {   // guard against multiple nested calls
            return;
        }
        tmpl.$('.topic-element').addClass('focus');
        showHideItemInput(tmpl);
    },

    'blur .topic-element'(evt, tmpl) {
        if (! tmpl.$('.topic-element').hasClass('focus')) { // guard against multiple nested calls
            return;
        }
        if (savingNewItem) {
            savingNewItem = false;
            return;
        }
        const nextElement = evt.relatedTarget;
        const topicElement = tmpl.find('.topic-element');
        if (!nextElement || !topicElement.contains(nextElement)) {
            tmpl.$('.topic-element').removeClass('focus');
            // Meteor.setTimeout(() => { showHideItemInput(tmpl, false); }, 500);
            showHideItemInput(tmpl, false);
        }
    },

    'click #btnDelTopic'(evt) {
        evt.preventDefault();

        if (!this.minutesID) {
            return;
        }
        console.log('Delete topics: '+this.topic._id+' from minutes '+this.minutesID);

        let aMin = new Minutes(this.minutesID);

        let topic = new Topic(this.minutesID, this.topic);
        const deleteAllowed = topic.isDeleteAllowed();

        if (!topic.isClosedAndHasNoOpenAIs() || deleteAllowed) {
            ConfirmationDialogFactory.makeWarningDialogWithTemplate(
                () => {
                    if (deleteAllowed) {
                        aMin.removeTopic(this.topic._id).catch(onError);
                    } else {
                        topic.closeTopicAndAllOpenActionItems().catch(onError);
                    }
                },
                deleteAllowed ? 'Confirm delete' : 'Close topic?',
                'confirmDeleteTopic',
                {
                    deleteAllowed: topic.isDeleteAllowed(),
                    hasOpenActionItems: topic.hasOpenActionItem(),
                    subject: topic.getSubject()
                },
                deleteAllowed ? 'Delete' : 'Close topic and actions'
            ).show();
        } else {
            ConfirmationDialogFactory.makeInfoDialog(
                'Cannot delete topic',
                'It is not possible to delete this topic because it was created in a previous minutes. ' +
                'The selected topic is already closed and has no open action items, so it won\'t be copied to the ' +
                'following minutes'
            ).show();
        }
    },

    'click .btnToggleState'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleState().catch(onError);
        });
    },

    'click .js-toggle-recurring'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleRecurring();
            aTopic.save().catch(onError);
        });
    },

    'click .js-toggle-skipped'(evt) {
        editTopicEventHandler(evt, this, (aTopic) => {
            aTopic.toggleSkip();
            aTopic.save().catch(onError);
        });
    },

    'click #btnEditTopic'(evt) {
        evt.preventDefault();
        if (!this.minutesID || getSelection().toString()) { // don't fire while selection is ongoing
            return;
        }
        Session.set('topicEditTopicId', this.topic._id);
        $('#dlgAddTopic').modal('show');
    },

    'click .addTopicInfoItem'(evt) {
        evt.preventDefault();
        // will be called before the modal dialog is shown
        openAddItemDialog('infoItem', this.topic._id);
    },

    'click .addTopicActionItem'(evt) {
        evt.preventDefault();
        // will be called before the modal dialog is shown
        openAddItemDialog('actionItem', this.topic._id);
    },

    'blur .addItemForm' (evt, tmpl) {
        if (!tmpl.data.isEditable) {
            throw new Meteor.Error('illegal-state', 'Tried to call an illegal event in read-only mode');
        }

        const theItemTextarea = tmpl.find('.add-item-field');
        const inputText = theItemTextarea.value;

        if (inputText === '') {
            return;
        }

        savingNewItem = true;
        const splitIndex = inputText.indexOf('\n');
        const subject = (splitIndex === -1) ? inputText : inputText.substring(0, splitIndex);
        const detail = (splitIndex === -1) ? '' : inputText.substring(splitIndex + 1).trim();

        const itemDoc = {
            subject: subject,
            responsibles: [],
            createdInMinute: this.minutesID
        };

        const topic = new Topic(this.minutesID, this.topic);
        const minutes = new Minutes(this.minutesID);
        const newItem = detectTypeAndCreateItem(itemDoc, topic, this.minutesID, minutes.parentMeetingSeries());
        if (detail) {
            newItem.addDetails(this.minutesID, detail);
        }
        newItem.saveAtBottom().catch(error => {
            theItemTextarea.value = inputText; // set desired value again!
            handleError(error);
        });

        // Clean & focus for next usage after saving last item
        theItemTextarea.value = '';
        resizeTextarea(theItemTextarea);
        theItemTextarea.focus();

        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        if (!collapseState) {
            collapseState = {};
        }
        collapseState[this.topic._id] = false;
        Session.set('minutesedit.collapsetopics.'+_minutesId, collapseState);
    },

    'keydown .addItemForm' (evt, tmpl) {
        const inputEl = tmpl.$('.add-item-field');
        if (evt.which === 13/*enter*/ && ( evt.ctrlKey || evt.metaKey)) {
            evt.preventDefault();
            inputEl.blur();
        }

        resizeTextarea(inputEl);
    },

    'keydown #btnTopicExpandCollapse'(evt) {
        evt.preventDefault();
        // since we do not have a link-href the link will not be clicked when hitting enter by default...
        if (evt.which === 13/*enter*/) {
            evt.currentTarget.click();
        }
    },

    'click #btnTopicExpandCollapse'(evt) {
        console.log('btnTopicExpandCollapse()'+this.topic._id);
        evt.preventDefault();
        let collapseState = Session.get('minutesedit.collapsetopics.'+_minutesId);
        if (!collapseState) {
            collapseState = {};
        }
        collapseState[this.topic._id] = ! collapseState[this.topic._id];
        Session.set('minutesedit.collapsetopics.'+_minutesId, collapseState);
    },

    'click #btnReopenTopic'(evt) {
        evt.preventDefault();
        let reopenTopic = () => {
            Meteor.call('workflow.reopenTopicFromMeetingSeries', this.parentMeetingSeriesId, this.topic._id);
        };
        ConfirmationDialogFactory.makeSuccessDialogWithTemplate(
            reopenTopic,
            'Re-open Topic',
            'confirmReOpenTopic',
            {
                topicSubject: Template.instance().data.topic.subject
            },
            'Re-open'
        ).show();
    }
});<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(_path, _fs) {
  'use strict';

  /////////////////////////////////////////////////////////////////////////////
  // DEFAULT API METHODS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Login Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    username    Username
   * @option  args      String    password    Password
   *
   * @return  void
   *
   * @api     api.login
   */
  module.exports.login = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Logout Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @return  void
   *
   * @api     api.logout
   */
  module.exports.logout = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Settings storing Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      Object    settings    Settings Tree
   *
   * @return  void
   *
   * @api     api.settings
   */
  module.exports.settings = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Application API Call
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    path        Package Path (ex: default/FileManager)
   * @option  args      String    method      API Method name
   * @option  args      Array     arguments   List of arguments to API Method
   *
   * @return  void
   *
   * @api     api.application
   */
  module.exports.application = function(args, callback, request, response, config) {
    var apath = args.path || null;
    var ameth = args.method || null;
    var aargs = args['arguments'] || [];

    var aroot = _path.join(config.repodir, apath);
    var fpath = _path.join(aroot, 'api.js');

    try {
      require(fpath)[ameth](aargs, function(error, result) {
        callback(error, result);
      }, request, response);
    } catch ( e ) {
      if ( config.logging !== false ) {
        console.warn(e.stack, e.trace);
      }
      callback('Application API error or missing: ' + e.toString(), null);
    }
  };

  /**
   * cURL API Call
   *
   * Gives an object like: {httpCode: -1, body: '...'}
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    method      HTTP Call method (GET/POST/HEAD)
   * @option  args      String    url         HTTP Call URL
   * @option  args      Object    query       HTTP POST Payload
   * @option  args      int       timeout     Timeout in seconds (default=0)
   * @option  args      boolean   binary      Return binary (default=false)
   * @option  args      String    mime        (Optional) If binary, which MIME
   * @option  args      Object    headers     (Optional) Custom HTTP headers ({key:val})
   * @option  args      boolean   json        (Optional) Send request as JSON (autodetected)
   *
   * @return  void
   * @link    http://os.js.org/doc/tutorials/using-curl.html
   *
   * @api     api.curl
   */
  module.exports.curl = function(args, callback, request, response, config) {
    var url = args.url;
    var method = args.method || 'GET';
    var query = args.query || {};
    var timeout = args.timeout || 0;
    var binary = args.binary === true;
    var mime = args.mime || null;
    var headers = args.headers || args.requestHeaders || {};

    if ( !mime && binary ) {
      mime = 'application/octet-stream';
    }

    if ( !url ) {
      callback('cURL expects an \'url\'');
      return;
    }

    var opts = {
      url: url,
      method: method,
      timeout: timeout * 1000,
      headers: headers
    };

    if ( method === 'POST' ) {
      if ( typeof query !== 'string' ) {
        opts.json = typeof opts.json === 'undefined' ? true : opts.json;
        opts.body = query;
      }
    }

    if ( binary ) {
      opts.encoding = null;
    }

    require('request')(opts, function(error, response, body) {
      if ( error ) {
        callback(error);
        return;
      }

      if ( binary && body ) {
        body = 'data:' + mime + ';base64,' + (body.toString('base64'));
      }

      var data = {
        httpCode: response.statusCode,
        body: body
      };

      callback(false, data);
    });
  };

})(
  require('path'),
  require('node-fs-extra')
);
<commit_msg>server-node: Updated curl api<commit_after>/*!
 * OS.js - JavaScript Cloud/Web Desktop Platform
 *
 * Copyright (c) 2011-2016, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(_path, _fs) {
  'use strict';

  /////////////////////////////////////////////////////////////////////////////
  // DEFAULT API METHODS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Login Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    username    Username
   * @option  args      String    password    Password
   *
   * @return  void
   *
   * @api     api.login
   */
  module.exports.login = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Logout Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @return  void
   *
   * @api     api.logout
   */
  module.exports.logout = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Settings storing Wrapper. This is just a placeholder. The function is bound in the handler
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      Object    settings    Settings Tree
   *
   * @return  void
   *
   * @api     api.settings
   */
  module.exports.settings = function(args, callback, request, response, config) {
    callback('No handler assigned', {});
  };

  /**
   * Application API Call
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    path        Package Path (ex: default/FileManager)
   * @option  args      String    method      API Method name
   * @option  args      Array     arguments   List of arguments to API Method
   *
   * @return  void
   *
   * @api     api.application
   */
  module.exports.application = function(args, callback, request, response, config) {
    var apath = args.path || null;
    var ameth = args.method || null;
    var aargs = args['arguments'] || [];

    var aroot = _path.join(config.repodir, apath);
    var fpath = _path.join(aroot, 'api.js');

    try {
      require(fpath)[ameth](aargs, function(error, result) {
        callback(error, result);
      }, request, response);
    } catch ( e ) {
      if ( config.logging !== false ) {
        console.warn(e.stack, e.trace);
      }
      callback('Application API error or missing: ' + e.toString(), null);
    }
  };

  /**
   * cURL API Call
   *
   * Gives an object like: {httpCode: -1, body: '...'}
   *
   * @param   Object    args        API Call Arguments
   * @param   Function  callback    Callback function => fn(error, result)
   * @param   Object    request     Server request object
   * @param   Object    response    Server response object
   * @param   Object    config      Server configuration object
   *
   * @option  args      String    method      HTTP Call method (GET/POST/HEAD)
   * @option  args      String    url         HTTP Call URL
   * @option  args      Object    query       HTTP POST Payload
   * @option  args      int       timeout     Timeout in seconds (default=0)
   * @option  args      boolean   binary      Return binary (default=false)
   * @option  args      String    mime        (Optional) If binary, which MIME
   * @option  args      Object    headers     (Optional) Custom HTTP headers ({key:val})
   * @option  args      boolean   json        (Optional) Send request as JSON (autodetected)
   *
   * @return  void
   * @link    http://os.js.org/doc/tutorials/using-curl.html
   *
   * @api     api.curl
   */
  module.exports.curl = function(args, callback, request, response, config) {
    var url = args.url;
    var method = args.method || 'GET';
    var query = args.query || {};
    var timeout = args.timeout || 0;
    var binary = args.binary === true;
    var mime = args.mime || null;
    var headers = args.headers || args.requestHeaders || {};

    if ( !mime && binary ) {
      mime = 'application/octet-stream';
    }

    if ( !url ) {
      callback('cURL expects an \'url\'');
      return;
    }

    var opts = {
      url: url,
      method: method,
      timeout: timeout * 1000,
      headers: headers
    };

    if ( method === 'POST' ) {
      if ( typeof query !== 'string' ) {
        opts.json = typeof opts.json === 'undefined' ? true : opts.json;
      }
      opts.body = query;
    }

    if ( binary ) {
      opts.encoding = null;
    }

    require('request')(opts, function(error, response, body) {
      if ( error ) {
        callback(error);
        return;
      }

      if ( binary && body ) {
        body = 'data:' + mime + ';base64,' + (body.toString('base64'));
      }

      var data = {
        httpCode: response.statusCode,
        body: body
      };

      callback(false, data);
    });
  };

})(
  require('path'),
  require('node-fs-extra')
);
<|endoftext|>