<commit_before>// ProcessService.js - in api/services
exports.process = function(json) {
    var nameMappings = {
        'ctun' : {
            'dcrt' : 'dcrate',
            'crt'  : 'crate',
        },
        'att' : {
            'desroll' : 'rollin',
            'despitch' : 'pitchin',
            'desyaw' : 'yawin',
        }
    }

    var processed = {
        params: {},
        att: {
            exists: false,
            mapped: false,
            rollin:  { col: null, values: [] },
            roll:    { col: null, values: [] },
            pitchin: { col: null, values: [] },
            pitch:   { col: null, values: [] },
            yawin:   { col: null, values: [] },
            yaw:     { col: null, values: [] },
            navyaw:  { col: null, values: [] },
        },
        atun: {
            exists: false,
            axis: [],
            tuneStep: [],
            rateMin: [],
            rateMax: [],
            rpGain: [],
            rdGain: [],
            spGain: []
        },
        atde: {
            exists: false,
            angle: [],
            rate: []
        },
        cam: {
            exists: false,
            cams: [], //array of cameras
            gpsTime: [],
            lat: [],
            lng: [],
            alt: [],
            roll: [],
            pitch: [],
            yaw: []
        },
        cmd: {
            exists: false,
            cmds: [], //array of command objects so can list them all
            cTol: [],
            cNum: [],
            cId: [],
            cOpt: [],
            prm1: [],
            alt: [],
            lat: [],
            lng: []
        },
        compass: {
            exists: false,
            x: [],
            magX: [],
            magY: [],
            magZ: [],
            ofsX: [],
            ofsY: [],
            ofsZ: [],
            mofsX: [],
            mofsY: [],
            mofsZ: []
        },
        curr: {
            exists:  false,
            mapped:  false,
            thr:     { col: null, values: [] },
            thrint:  { col: null, values: [] },
            volt:    { col: null, values: [] },
            curr:    { col: null, values: [] },
            vcc:     { col: null, values: [] },
            currtot: { col: null, values: [] },
            totcur: 0,
            avgcur: 0,
        },
        ctun: {
            exists: false,
            mapped: false,
            thrin:  { col: null, values: [] },
            sonalt: { col: null, values: [] },
            baralt: { col: null, values: [] },
            wpalt:  { col: null, values: [] },
            navthr: { col: null, values: [] },
            angbst: { col: null, values: [] },
            crate:  { col: null, values: [] },
            throut: { col: null, values: [] },
            dcrate: { col: null, values: [] }
        },
        err: {
            exists: false,
            errs: [],
        },
        ev: {
            exists: false,
            evt: [],
        },
        gps: {
            exists: false,
            mapped: false,
            status: { col: null, values: [] },
            time:   { col: null, values: [] },
            nsats:  { col: null, values: [] },
            hdop:   { col: null, values: [] },
            lat:    { col: null, values: [] },
            lng:    { col: null, values: [] },
            relalt: { col: null, values: [] },
            alt:    { col: null, values: [] },
            spd:    { col: null, values: [] },
            gcrs:   { col: null, values: [] },
            avgSpd: 0,
            lAvgSpd: [],
            googleMaps: [],
            readings: [],
        },
        imu: {
            exists: false,
            timems: { col: null, values: [] },
            gyrx: { col: null, values: [] },
            gyry: { col: null, values: [] },
            gyrz: { col: null, values: [] },
            accx: { col: null, values: [] },
            accy: { col: null, values: [] },
            accz: { col: null, values: [] },
        },
        inav: {
            exists: false,
            barAlt: [],
            iAlt: [],
            iClb: [],
            aCorX: [],
            aCorY: [],
            aCorZ: [],
            gLat: [],
            gLng: [],
            iLat: [],
            iLng: [],
        },
        mode: {
            count: 0,
            mode: { col: null },
            modes: {}, //array of modes!
        },
        ntun: {
            exists: false,
            wpDst: [],
            wpBrg: [],
            perX: [],
            perY: [],
            dVelX: [],
            dVelY: [],
            velX: [],
            velY: [],
            dAcX: [],
            dAcY: [],
            dRol: [],
            dpit: [],
        },
        pm: {
            exists: false,
            renCnt: [],
            renBlw: [],
            fixCnt: [],
            nLon: [],
            nLoop: [],
            maxT: [],
            pmt: [],
            i2cErr: [],
        }
    };

    for (var k in json) {
        var row = json[k],
        rowNum = parseInt(k);

        switch (row[0]) {
            case 'PARM':
                var name = row[1].trim().toLowerCase();
                processed.params[name] = {'name': row[1], 'value': row[2]};
                break;

            case 'FMT':
                var val = row[3].trim().toLowerCase();

                if (typeof processed[val] != "undefined") {
                    for (var i in row) {
                        var p = row[i].trim().toLowerCase();
                        if (typeof nameMappings[val] != "undefined" && typeof nameMappings[val][p] != "undefined") {
                            p = nameMappings[val][p];
                        }
                        if (typeof processed[val][p] != "undefined") {
                            processed[val][p].col = i - 4;
                        }
                    };
                    processed[val].mapped = true;
                }

                break;

            case 'ATT':
                processed.att.exists = true;
                processed.att.rollin.values.push(  [rowNum, parseFloat(row[processed.att.rollin.col])]);
                processed.att.roll.values.push(    [rowNum, parseFloat(row[processed.att.roll.col])]);
                processed.att.pitchin.values.push( [rowNum, parseFloat(row[processed.att.pitchin.col])]);
                processed.att.pitch.values.push(   [rowNum, parseFloat(row[processed.att.pitch.col])]);
                processed.att.yawin.values.push(   [rowNum, parseFloat(row[processed.att.yawin.col])]);
                processed.att.yaw.values.push(     [rowNum, parseFloat(row[processed.att.yaw.col])]);
                processed.att.navyaw.values.push(  [rowNum, parseFloat(row[processed.att.navyaw.col])]);
                break;

            case 'ATUN':

                break;

            case 'ATDE':

                break;

            case 'CAM':

                break;

            case 'CMD':

                break;

            case 'COMPASS':

                break;

            case 'CURR':
                processed.curr.exists = true;
                processed.curr.thr.values.push(     [rowNum, parseFloat(row[processed.curr.thr.col])]);
                processed.curr.thrint.values.push(  [rowNum, parseFloat(row[processed.curr.thrint.col])]);
                processed.curr.volt.values.push(    [rowNum, parseFloat(row[processed.curr.volt.col])/100]);
                processed.curr.curr.values.push(    [rowNum, parseFloat(row[processed.curr.curr.col])/100]);
                processed.curr.vcc.values.push(     [rowNum, parseFloat(row[processed.curr.vcc.col])/1000]);
                processed.curr.currtot.values.push( [rowNum, parseFloat(row[processed.curr.currtot.col])]);

                processed.curr.avgcur += parseFloat(row[processed.curr.curr.col]/100);
                processed.curr.totcur =  parseFloat(row[processed.curr.currtot.col]);

                break;

            case 'CTUN':
                processed.ctun.exists = true;
                processed.ctun.thrin.values.push(  [rowNum, parseFloat(row[processed.ctun.thrin.col])]);
                processed.ctun.sonalt.values.push( [rowNum, parseFloat(row[processed.ctun.sonalt.col])]);
                processed.ctun.baralt.values.push( [rowNum, parseFloat(row[processed.ctun.baralt.col])]);
                processed.ctun.wpalt.values.push(  [rowNum, parseFloat(row[processed.ctun.wpalt.col])]);
                processed.ctun.navthr.values.push( [rowNum, parseFloat(row[processed.ctun.navthr.col])]);
                processed.ctun.angbst.values.push( [rowNum, parseFloat(row[processed.ctun.angbst.col])]);
                processed.ctun.crate.values.push(  [rowNum, parseFloat(row[processed.ctun.crate.col])]);
                processed.ctun.throut.values.push( [rowNum, parseFloat(row[processed.ctun.throut.col])]);
                processed.ctun.dcrate.values.push( [rowNum, parseFloat(row[processed.ctun.dcrate.col])]);
                break;

            case 'ERR':
                processed.err.exists = true;
                error = {
                  error: parseInt(row[1]),
                  eCode: parseInt(row[2]),
                  type: 'unknown',
                  msg: 'unknown'
                };
                //http://copter.ardupilot.com/wiki/common-diagnosing-problems-using-logs/#Unexpected_ERRORS_including_Failsafes
                switch(error.error) {
                  case 1: //Main (never used)
                    break;
                  case 2://Radio
                    error.type = 'Radio';
                    if (error.eCode = 1) {
                      error.msg = "'Late Frame' which means the APM's onboard ppm encoder did not provide an update for at least 2 seconds";
                    } else if (error.eCode = 0) {
                      error.msg = "error resolved which means the ppm encoder started providing data again";
                    }
                    break;
                  case 3:
                    error.type = "Compass";
                    break;
                  case 4:
                    error.type = "Optical flow";
                    break;
                  case 5:
                    error.type = "Throttle failsafe";
                    if (error.eCode = 1) {
                        error.msg = "throttle dropped below FS_THR_VALUE meaning likely loss of contact between RX/TX";
                    } else if (error.eCode = 0) {
                        error.msg = "above error resolve meaning RX/TX contact likely restored";
                    }
                    break;
                  case 6: //Battery failsafe
                    error.type = "Battery failsafe"
                    if (error.eCode = 1) {
                      error.msg = "battery voltage dropped below LOW_VOLT or total battery capacity used exceeded BATT_CAPACITY";
                    }
                    break;
                  case 7:
                    error.type = "GPS failsafe";
                    var flightModeErrs = [
                        "GPS lock restored",
                        "GPS lock lost for at least 5 seconds"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 8:
                    error.type = "GCS (Ground station) failsafe";
                    break;
                  case 9:
                    error.type = "Fence";
                    break;
                  case 10:
                    error.type = "Flight Mode";
                    var flightModeErrs = [
                        "the vehicle was unable to enter the Stabilize flight mode",
                        "the vehicle was unable to enter the Acro flight mode",
                        "the vehicle was unable to enter the AltHold flight mode",
                        "the vehicle was unable to enter the Auto flight mode",
                        "the vehicle was unable to enter the Guided flight mode",
                        "the vehicle was unable to enter the Loiter flight mode",
                        "the vehicle was unable to enter the RTL flight mode",
                        "the vehicle was unable to enter the Circle flight mode",
                        "the vehicle was unable to enter the Position flight mode",
                        "the vehicle was unable to enter the Land flight mode",
                        "the vehicle was unable to enter the OF_Loiter flight mode"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 11:
                    error.type = "GPS";
                    var flightModeErrs = [
                        "GPS Glitch cleared",
                        "",
                        "GPS Glitch"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 12:
                    error.type = "Crash Check";
                    if (error.eCode == 1) {
                        error.msg = "Crash detected";
                    }
                    break;
                }
                processed.err.errs.push(error);
                break;

            case 'EV':

                break;

            case 'GPS':
                processed.gps.exists = true;

                processed.gps.status.values.push( [rowNum, parseFloat(row[processed.gps.status.col])]);
                processed.gps.time.values.push(   [rowNum, parseFloat(row[processed.gps.time.col])]);
                processed.gps.nsats.values.push(  [rowNum, parseFloat(row[processed.gps.nsats.col])]);
                processed.gps.hdop.values.push(   [rowNum, parseFloat(row[processed.gps.hdop.col])]);
                processed.gps.lat.values.push(    [rowNum, parseFloat(row[processed.gps.lat.col])]);
                processed.gps.lng.values.push(    [rowNum, parseFloat(row[processed.gps.lng.col])]);
                processed.gps.relalt.values.push( [rowNum, parseFloat(row[processed.gps.relalt.col])]);
                processed.gps.alt.values.push(    [rowNum, parseFloat(row[processed.gps.alt.col])]);
                processed.gps.spd.values.push(    [rowNum, parseFloat(row[processed.gps.spd.col])]);
                processed.gps.gcrs.values.push(   [rowNum, parseFloat(row[processed.gps.gcrs.col])]);

                processed.gps.avgSpd += parseFloat(row[processed.gps.spd.col]);
                processed.gps.lAvgSpd.push([rowNum, processed.gps.avgSpd / processed.gps.spd.values.length]);

                processed.gps.googleMaps.push([parseFloat(row[processed.gps.lat.col]),  parseFloat(row[processed.gps.lng.col])]);

                processed.gps.readings.push(rowNum);
                break;

            case 'IMU':
                processed.imu.exists = true;
                processed.imu.timems.values.push( [rowNum, parseFloat(row[processed.imu.timems.col])]);
                processed.imu.gyrx.values.push( [rowNum, parseFloat(row[processed.imu.gyrx.col])]);
                processed.imu.gyry.values.push( [rowNum, parseFloat(row[processed.imu.gyry.col])]);
                processed.imu.gyrz.values.push( [rowNum, parseFloat(row[processed.imu.gyrz.col])]);
                processed.imu.accx.values.push( [rowNum, parseFloat(row[processed.imu.accx.col])]);
                processed.imu.accy.values.push( [rowNum, parseFloat(row[processed.imu.accy.col])]);
                processed.imu.accz.values.push( [rowNum, parseFloat(row[processed.imu.accz.col])]);
                break;

            case 'INAV':

                break;

            case 'MODE':
                var mode = {
                    'name' : row[processed.mode.mode.col].trim(),
                    'thrCrs' : parseFloat(row[2]),
                    'start': rowNum,
                    'end' : false,
                },
                next = processed.mode.count + 1;

                processed.mode.modes[next] = mode;

                if (processed.mode.count > 0) {
                    processed.mode.modes[processed.mode.count].end = rowNum;
                }

                processed.mode.count = next;
                break;

            case 'NTUN':

                break;

            case 'PM':

                break;

        }

    }

    //Clean up the last mode
    if (processed.mode.modes[processed.mode.count] != null) {
        processed.mode.modes[processed.mode.count].end = rowNum;
    }

    processed.gps.avgSpd = (processed.gps.avgSpd / processed.gps.spd.values.length).toFixed(2);
    processed.curr.avgcur = (processed.curr.avgcur / processed.curr.curr.values.length).toFixed(2);
    processed.curr.totcur = processed.curr.totcur.toFixed(2);

    return processed;

};
<commit_msg>Temp mode fix<commit_after>// ProcessService.js - in api/services
exports.process = function(json) {
    var nameMappings = {
        'ctun' : {
            'dcrt' : 'dcrate',
            'crt'  : 'crate',
        },
        'att' : {
            'desroll' : 'rollin',
            'despitch' : 'pitchin',
            'desyaw' : 'yawin',
        }
    }

    var processed = {
        params: {},
        att: {
            exists: false,
            mapped: false,
            rollin:  { col: null, values: [] },
            roll:    { col: null, values: [] },
            pitchin: { col: null, values: [] },
            pitch:   { col: null, values: [] },
            yawin:   { col: null, values: [] },
            yaw:     { col: null, values: [] },
            navyaw:  { col: null, values: [] },
        },
        atun: {
            exists: false,
            axis: [],
            tuneStep: [],
            rateMin: [],
            rateMax: [],
            rpGain: [],
            rdGain: [],
            spGain: []
        },
        atde: {
            exists: false,
            angle: [],
            rate: []
        },
        cam: {
            exists: false,
            cams: [], //array of cameras
            gpsTime: [],
            lat: [],
            lng: [],
            alt: [],
            roll: [],
            pitch: [],
            yaw: []
        },
        cmd: {
            exists: false,
            cmds: [], //array of command objects so can list them all
            cTol: [],
            cNum: [],
            cId: [],
            cOpt: [],
            prm1: [],
            alt: [],
            lat: [],
            lng: []
        },
        compass: {
            exists: false,
            x: [],
            magX: [],
            magY: [],
            magZ: [],
            ofsX: [],
            ofsY: [],
            ofsZ: [],
            mofsX: [],
            mofsY: [],
            mofsZ: []
        },
        curr: {
            exists:  false,
            mapped:  false,
            thr:     { col: null, values: [] },
            thrint:  { col: null, values: [] },
            volt:    { col: null, values: [] },
            curr:    { col: null, values: [] },
            vcc:     { col: null, values: [] },
            currtot: { col: null, values: [] },
            totcur: 0,
            avgcur: 0,
        },
        ctun: {
            exists: false,
            mapped: false,
            thrin:  { col: null, values: [] },
            sonalt: { col: null, values: [] },
            baralt: { col: null, values: [] },
            wpalt:  { col: null, values: [] },
            navthr: { col: null, values: [] },
            angbst: { col: null, values: [] },
            crate:  { col: null, values: [] },
            throut: { col: null, values: [] },
            dcrate: { col: null, values: [] }
        },
        err: {
            exists: false,
            errs: [],
        },
        ev: {
            exists: false,
            evt: [],
        },
        gps: {
            exists: false,
            mapped: false,
            status: { col: null, values: [] },
            time:   { col: null, values: [] },
            nsats:  { col: null, values: [] },
            hdop:   { col: null, values: [] },
            lat:    { col: null, values: [] },
            lng:    { col: null, values: [] },
            relalt: { col: null, values: [] },
            alt:    { col: null, values: [] },
            spd:    { col: null, values: [] },
            gcrs:   { col: null, values: [] },
            avgSpd: 0,
            lAvgSpd: [],
            googleMaps: [],
            readings: [],
        },
        imu: {
            exists: false,
            timems: { col: null, values: [] },
            gyrx: { col: null, values: [] },
            gyry: { col: null, values: [] },
            gyrz: { col: null, values: [] },
            accx: { col: null, values: [] },
            accy: { col: null, values: [] },
            accz: { col: null, values: [] },
        },
        inav: {
            exists: false,
            barAlt: [],
            iAlt: [],
            iClb: [],
            aCorX: [],
            aCorY: [],
            aCorZ: [],
            gLat: [],
            gLng: [],
            iLat: [],
            iLng: [],
        },
        mode: {
            count: 0,
            mode: { col: null },
            modes: {}, //array of modes!
        },
        ntun: {
            exists: false,
            wpDst: [],
            wpBrg: [],
            perX: [],
            perY: [],
            dVelX: [],
            dVelY: [],
            velX: [],
            velY: [],
            dAcX: [],
            dAcY: [],
            dRol: [],
            dpit: [],
        },
        pm: {
            exists: false,
            renCnt: [],
            renBlw: [],
            fixCnt: [],
            nLon: [],
            nLoop: [],
            maxT: [],
            pmt: [],
            i2cErr: [],
        }
    };

    for (var k in json) {
        var row = json[k],
        rowNum = parseInt(k);

        switch (row[0]) {
            case 'PARM':
                var name = row[1].trim().toLowerCase();
                processed.params[name] = {'name': row[1], 'value': row[2]};
                break;

            case 'FMT':
                var val = row[3].trim().toLowerCase();

                if (typeof processed[val] != "undefined") {
                    for (var i in row) {
                        var p = row[i].trim().toLowerCase();
                        if (typeof nameMappings[val] != "undefined" && typeof nameMappings[val][p] != "undefined") {
                            p = nameMappings[val][p];
                        }
                        if (typeof processed[val][p] != "undefined") {
                            processed[val][p].col = i - 4;
                        }
                    };
                    processed[val].mapped = true;
                }

                break;

            case 'ATT':
                processed.att.exists = true;
                processed.att.rollin.values.push(  [rowNum, parseFloat(row[processed.att.rollin.col])]);
                processed.att.roll.values.push(    [rowNum, parseFloat(row[processed.att.roll.col])]);
                processed.att.pitchin.values.push( [rowNum, parseFloat(row[processed.att.pitchin.col])]);
                processed.att.pitch.values.push(   [rowNum, parseFloat(row[processed.att.pitch.col])]);
                processed.att.yawin.values.push(   [rowNum, parseFloat(row[processed.att.yawin.col])]);
                processed.att.yaw.values.push(     [rowNum, parseFloat(row[processed.att.yaw.col])]);
                processed.att.navyaw.values.push(  [rowNum, parseFloat(row[processed.att.navyaw.col])]);
                break;

            case 'ATUN':

                break;

            case 'ATDE':

                break;

            case 'CAM':

                break;

            case 'CMD':

                break;

            case 'COMPASS':

                break;

            case 'CURR':
                processed.curr.exists = true;
                processed.curr.thr.values.push(     [rowNum, parseFloat(row[processed.curr.thr.col])]);
                processed.curr.thrint.values.push(  [rowNum, parseFloat(row[processed.curr.thrint.col])]);
                processed.curr.volt.values.push(    [rowNum, parseFloat(row[processed.curr.volt.col])/100]);
                processed.curr.curr.values.push(    [rowNum, parseFloat(row[processed.curr.curr.col])/100]);
                processed.curr.vcc.values.push(     [rowNum, parseFloat(row[processed.curr.vcc.col])/1000]);
                processed.curr.currtot.values.push( [rowNum, parseFloat(row[processed.curr.currtot.col])]);

                processed.curr.avgcur += parseFloat(row[processed.curr.curr.col]/100);
                processed.curr.totcur =  parseFloat(row[processed.curr.currtot.col]);

                break;

            case 'CTUN':
                processed.ctun.exists = true;
                processed.ctun.thrin.values.push(  [rowNum, parseFloat(row[processed.ctun.thrin.col])]);
                processed.ctun.sonalt.values.push( [rowNum, parseFloat(row[processed.ctun.sonalt.col])]);
                processed.ctun.baralt.values.push( [rowNum, parseFloat(row[processed.ctun.baralt.col])]);
                processed.ctun.wpalt.values.push(  [rowNum, parseFloat(row[processed.ctun.wpalt.col])]);
                processed.ctun.navthr.values.push( [rowNum, parseFloat(row[processed.ctun.navthr.col])]);
                processed.ctun.angbst.values.push( [rowNum, parseFloat(row[processed.ctun.angbst.col])]);
                processed.ctun.crate.values.push(  [rowNum, parseFloat(row[processed.ctun.crate.col])]);
                processed.ctun.throut.values.push( [rowNum, parseFloat(row[processed.ctun.throut.col])]);
                processed.ctun.dcrate.values.push( [rowNum, parseFloat(row[processed.ctun.dcrate.col])]);
                break;

            case 'ERR':
                processed.err.exists = true;
                error = {
                  error: parseInt(row[1]),
                  eCode: parseInt(row[2]),
                  type: 'unknown',
                  msg: 'unknown'
                };
                //http://copter.ardupilot.com/wiki/common-diagnosing-problems-using-logs/#Unexpected_ERRORS_including_Failsafes
                switch(error.error) {
                  case 1: //Main (never used)
                    break;
                  case 2://Radio
                    error.type = 'Radio';
                    if (error.eCode = 1) {
                      error.msg = "'Late Frame' which means the APM's onboard ppm encoder did not provide an update for at least 2 seconds";
                    } else if (error.eCode = 0) {
                      error.msg = "error resolved which means the ppm encoder started providing data again";
                    }
                    break;
                  case 3:
                    error.type = "Compass";
                    break;
                  case 4:
                    error.type = "Optical flow";
                    break;
                  case 5:
                    error.type = "Throttle failsafe";
                    if (error.eCode = 1) {
                        error.msg = "throttle dropped below FS_THR_VALUE meaning likely loss of contact between RX/TX";
                    } else if (error.eCode = 0) {
                        error.msg = "above error resolve meaning RX/TX contact likely restored";
                    }
                    break;
                  case 6: //Battery failsafe
                    error.type = "Battery failsafe"
                    if (error.eCode = 1) {
                      error.msg = "battery voltage dropped below LOW_VOLT or total battery capacity used exceeded BATT_CAPACITY";
                    }
                    break;
                  case 7:
                    error.type = "GPS failsafe";
                    var flightModeErrs = [
                        "GPS lock restored",
                        "GPS lock lost for at least 5 seconds"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 8:
                    error.type = "GCS (Ground station) failsafe";
                    break;
                  case 9:
                    error.type = "Fence";
                    break;
                  case 10:
                    error.type = "Flight Mode";
                    var flightModeErrs = [
                        "the vehicle was unable to enter the Stabilize flight mode",
                        "the vehicle was unable to enter the Acro flight mode",
                        "the vehicle was unable to enter the AltHold flight mode",
                        "the vehicle was unable to enter the Auto flight mode",
                        "the vehicle was unable to enter the Guided flight mode",
                        "the vehicle was unable to enter the Loiter flight mode",
                        "the vehicle was unable to enter the RTL flight mode",
                        "the vehicle was unable to enter the Circle flight mode",
                        "the vehicle was unable to enter the Position flight mode",
                        "the vehicle was unable to enter the Land flight mode",
                        "the vehicle was unable to enter the OF_Loiter flight mode"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 11:
                    error.type = "GPS";
                    var flightModeErrs = [
                        "GPS Glitch cleared",
                        "",
                        "GPS Glitch"
                    ];
                    error.msg = flightModeErrs[error.eCode];
                    break;
                  case 12:
                    error.type = "Crash Check";
                    if (error.eCode == 1) {
                        error.msg = "Crash detected";
                    }
                    break;
                }
                processed.err.errs.push(error);
                break;

            case 'EV':

                break;

            case 'GPS':
                processed.gps.exists = true;

                processed.gps.status.values.push( [rowNum, parseFloat(row[processed.gps.status.col])]);
                processed.gps.time.values.push(   [rowNum, parseFloat(row[processed.gps.time.col])]);
                processed.gps.nsats.values.push(  [rowNum, parseFloat(row[processed.gps.nsats.col])]);
                processed.gps.hdop.values.push(   [rowNum, parseFloat(row[processed.gps.hdop.col])]);
                processed.gps.lat.values.push(    [rowNum, parseFloat(row[processed.gps.lat.col])]);
                processed.gps.lng.values.push(    [rowNum, parseFloat(row[processed.gps.lng.col])]);
                processed.gps.relalt.values.push( [rowNum, parseFloat(row[processed.gps.relalt.col])]);
                processed.gps.alt.values.push(    [rowNum, parseFloat(row[processed.gps.alt.col])]);
                processed.gps.spd.values.push(    [rowNum, parseFloat(row[processed.gps.spd.col])]);
                processed.gps.gcrs.values.push(   [rowNum, parseFloat(row[processed.gps.gcrs.col])]);

                processed.gps.avgSpd += parseFloat(row[processed.gps.spd.col]);
                processed.gps.lAvgSpd.push([rowNum, processed.gps.avgSpd / processed.gps.spd.values.length]);

                processed.gps.googleMaps.push([parseFloat(row[processed.gps.lat.col]),  parseFloat(row[processed.gps.lng.col])]);

                processed.gps.readings.push(rowNum);
                break;

            case 'IMU':
                processed.imu.exists = true;
                processed.imu.timems.values.push( [rowNum, parseFloat(row[processed.imu.timems.col])]);
                processed.imu.gyrx.values.push( [rowNum, parseFloat(row[processed.imu.gyrx.col])]);
                processed.imu.gyry.values.push( [rowNum, parseFloat(row[processed.imu.gyry.col])]);
                processed.imu.gyrz.values.push( [rowNum, parseFloat(row[processed.imu.gyrz.col])]);
                processed.imu.accx.values.push( [rowNum, parseFloat(row[processed.imu.accx.col])]);
                processed.imu.accy.values.push( [rowNum, parseFloat(row[processed.imu.accy.col])]);
                processed.imu.accz.values.push( [rowNum, parseFloat(row[processed.imu.accz.col])]);
                break;

            case 'INAV':

                break;

            case 'MODE':
                if (processed.mode.mode.col != null) {
                    var mode = {
                        'name' : row[processed.mode.mode.col].trim(),
                        'thrCrs' : parseFloat(row[2]),
                        'start': rowNum,
                        'end' : false,
                    },
                    next = processed.mode.count + 1;

                    processed.mode.modes[next] = mode;

                    if (processed.mode.count > 0) {
                        processed.mode.modes[processed.mode.count].end = rowNum;
                    }

                    processed.mode.count = next;
                }
                break;

            case 'NTUN':

                break;

            case 'PM':

                break;

        }

    }

    //Clean up the last mode
    if (processed.mode.modes[processed.mode.count] != null) {
        processed.mode.modes[processed.mode.count].end = rowNum;
    }

    processed.gps.avgSpd = (processed.gps.avgSpd / processed.gps.spd.values.length).toFixed(2);
    processed.curr.avgcur = (processed.curr.avgcur / processed.curr.curr.values.length).toFixed(2);
    processed.curr.totcur = processed.curr.totcur.toFixed(2);

    return processed;

};
<|endoftext|>


<commit_before>/**
 * TODO: Some times the browser's pop event fires after state change success. Does it affect things?
 */
angular.module( 'gj.Scroll.AutoScroll' ).directive( 'gjAutoScroll', function( $q, $window, $document, $timeout, $parse, $position, AutoScroll, $state )
{
	return {
		link: function( scope, element, attrs )
		{
			var startWatcher, successWatcher, stateWatcher;
			var popEventFired = false;

			// If this element is the document body, then set the element to scroll to be the document.
			// This is what the browser scrolls by default.
			var scrollElem = element;
			if ( scrollElem[0] == $document[0].body ) {
				scrollElem = $document;
			}

			// http://stackoverflow.com/questions/16355549/window-history-length-maximum-value/16603692#16603692
			// https://developer.mozilla.org/en-US/docs/WindowEventHandlers.onpopstate
			function watchPop( event )
			{
				popEventFired = true;
			}

			function doScroll( to )
			{
				// Only do this if a no-scroll wasn't set.
				// This can be set by a directive that will cancel out the auto-scrolling behavior for one auto-scroll cycle.
				if ( !AutoScroll.noScroll() ) {

					// Check to see if we have a saved history state for the page we're going to.
					// If the popstate event fired it means the user was either going back or forward through history manually
					// in the browser. In that case, we want to attempt to scroll.
					var state = AutoScroll.getState( to );
					if ( popEventFired && state && state.scroll > 0 ) {

						// We need to let angular compile before attempting to scroll.
						// TODO: This causes a flicker of content before scrolling. Would be great to somehow get rid of this flicker.
						$timeout( function()
						{
							scrollElem.scrollTop( state.scroll );
						}, 0 );
					}
					else {
						var anchor = AutoScroll.anchor();
						if ( anchor ) {

							// We only scroll to the anchor if they're scrolled past it currently.
							var offset = $position.offset( anchor );
							if ( scrollElem.scrollTop() > offset.top ) {
								scrollElem.scrollTop( offset.top );
							}
						}
						else {
							scrollElem.scrollTop( 0 );
						}
					}
				}

				popEventFired = false;

				// Clear out no-scroll that may be set now that we've gone to a new location.
				AutoScroll.noScroll( false );
			}

			function registerWatchers()
			{
				startWatcher = scope.$on( '$stateChangeStart', function( $event )
				{
					if ( arguments[1] ) {
						AutoScroll.pushState( $state.href( arguments[3], arguments[4] ), scrollElem );
					}
				} );

				successWatcher = scope.$on( '$stateChangeSuccess', function()
				{
					doScroll( $state.href( arguments[1], arguments[2] ) );
				} );

				angular.element( $window ).on( 'popstate', watchPop );
			}

			function deregisterWatchers()
			{
				if ( startWatcher ) {
					startWatcher();
				}

				if ( successWatcher ) {
					successWatcher();
				}

				angular.element( $window ).off( 'popstate', watchPop );
			}

			// We only activate when the directive attribute evaluates to true.
			// Or if the directive as attached with no watch value.
			scope.$watch( $parse( attrs.gjAutoScroll ), function( isActive )
			{
				if ( isActive || angular.isUndefined( isActive ) ) {
					registerWatchers();
				}
				else {
					deregisterWatchers();
				}
			} );

			scope.$on( '$destroy', function()
			{
				deregisterWatchers();
			} );
		}
	};
} );
<commit_msg>Changed autoscroll service to use the new History service instead of managing it's own popstate checking.<commit_after>angular.module( 'gj.Scroll.AutoScroll' ).directive( 'gjAutoScroll', function( $q, $window, $document, $timeout, $parse, $position, AutoScroll, History, $state )
{
	return {
		link: function( scope, element, attrs )
		{
			var startWatcher, successWatcher, stateWatcher;
			// var popEventFired = false;

			// If this element is the document body, then set the element to scroll to be the document.
			// This is what the browser scrolls by default.
			var scrollElem = element;
			if ( scrollElem[0] == $document[0].body ) {
				scrollElem = $document;
			}

			// http://stackoverflow.com/questions/16355549/window-history-length-maximum-value/16603692#16603692
			// https://developer.mozilla.org/en-US/docs/WindowEventHandlers.onpopstate
			// function watchPop( event )
			// {
			// 	popEventFired = true;
			// }

			function doScroll( to )
			{
				// Only do this if a no-scroll wasn't set.
				// This can be set by a directive that will cancel out the auto-scrolling behavior for one auto-scroll cycle.
				if ( !AutoScroll.noScroll() ) {

					// Check to see if we have a saved history state for the page we're going to.
					// If the popstate event fired it means the user was either going back or forward through history manually
					// in the browser. In that case, we want to attempt to scroll.
					var state = AutoScroll.getState( to );
					if ( History.inHistorical && state && state.scroll > 0 ) {

						// We need to let angular compile before attempting to scroll.
						// TODO: This causes a flicker of content before scrolling. Would be great to somehow get rid of this flicker.
						$timeout( function()
						{
							console.log( 'set scroll top' );
							scrollElem.scrollTop( state.scroll );
						}, 0, false );
					}
					else {
						var anchor = AutoScroll.anchor();
						if ( anchor ) {

							console.log( 'anchor scroll' );

							// We only scroll to the anchor if they're scrolled past it currently.
							var offset = $position.offset( anchor );
							if ( scrollElem.scrollTop() > offset.top ) {
								scrollElem.scrollTop( offset.top );
							}
						}
						else {
							console.log( 'reset scroll top' );
							$timeout( function()
							{
								scrollElem.scrollTop( 0 );
							}, 0, false );
						}
					}
				}

				// popEventFired = false;

				// Clear out no-scroll that may be set now that we've gone to a new location.
				AutoScroll.noScroll( false );
			}

			function registerWatchers()
			{
				startWatcher = scope.$on( '$stateChangeStart', function( $event )
				{
					if ( arguments[1] ) {
						AutoScroll.pushState( $state.href( arguments[3], arguments[4] ), scrollElem );
					}
				} );

				successWatcher = scope.$on( '$stateChangeSuccess', function()
				{
					doScroll( $state.href( arguments[1], arguments[2] ) );
				} );

				// angular.element( $window ).on( 'popstate', watchPop );
			}

			function deregisterWatchers()
			{
				if ( startWatcher ) {
					startWatcher();
				}

				if ( successWatcher ) {
					successWatcher();
				}

				// angular.element( $window ).off( 'popstate', watchPop );
			}

			// We only activate when the directive attribute evaluates to true.
			// Or if the directive as attached with no watch value.
			scope.$watch( $parse( attrs.gjAutoScroll ), function( isActive )
			{
				if ( isActive || angular.isUndefined( isActive ) ) {
					registerWatchers();
				}
				else {
					deregisterWatchers();
				}
			} );

			scope.$on( '$destroy', function()
			{
				deregisterWatchers();
			} );
		}
	};
} );
<|endoftext|>


<commit_before>import decode from 'jwt-decode'
import { Forbidden } from '@feathersjs/errors'
import { Ability, AbilityBuilder } from '@casl/ability'
import { permittedFieldsOf } from '@casl/ability/extra'
import _ from 'lodash'

Ability.addAlias('update', 'patch')
Ability.addAlias('read', ['get', 'find'])
Ability.addAlias('delete', 'remove')

// hardcoded roles that must match roles defined in database (for now)
const ROLE_USER = 'user'
const ROLE_ADMIN = 'admin'
const ROLE_SUPERADMIN = 'superadmin'

const subjectName = (subject) =>
  !subject || typeof subject === 'string' ? subject : subject.type()

const extractRolesFromJwtToken = (ctx) => {
  if (ctx.params.headers && ctx.params.headers.authorization) {
    return decode(ctx.params.headers.authorization).roles
  }
  if (ctx.result && ctx.result.accessToken) {
    return decode(ctx.result.accessToken).roles
  }
  return []
}

const defineAbilities = (ctx) => {
  const roles = extractRolesFromJwtToken(ctx)

  if (!ctx.params.user) {
    ctx.params.user = {
      id: -1,
      name: 'guest',
    }
  }

  const userId = ctx.params.user.id

  ctx.app.debug(
    roles.length > 0 ? roles.map((r) => r.name) : 'none',
    `authorized user ${userId} ${ctx.params.user.name} with roles`
  )

  const hasRole = (role) => roles.find((r) => r.name === role)

  const { rules, can } = AbilityBuilder.extract()

  const WRITABLE_DEPOT_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'farms',
    'deliveryDays',
  ]
  const READABLE_DEPOT_ATTRIBUTES = [
    ...WRITABLE_DEPOT_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  const WRITABLE_FARM_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'depots',
    'acceptsNewMembers',
    'foundedAtYear',
    'foundedAtMonth',
    'maximumMembers',
    'additionalProductInformation',
    'participation',
    'actsEcological',
    'economicalBehavior',
    'products',
    'badges',
  ]
  const READABLE_FARM_ATTRIBUTES = [
    ...WRITABLE_FARM_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  const WRITABLE_INITIATIVE_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'goals',
    'badges',
  ]
  const READABLE_INITIATIVE_ATTRIBUTES = [
    ...WRITABLE_INITIATIVE_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  can('read', 'admin/jobs')
  can('manage', 'admin/products')
  can('manage', 'admin/farms')

  // admin backend: API permissions
  if (hasRole(ROLE_SUPERADMIN)) {
    can('manage', 'admin/goals')
    can('manage', 'admin/badges')
    can('manage', 'admin/products')
  } else if (hasRole(ROLE_ADMIN)) {
    can('read', 'admin/goals')
    can('read', 'admin/badges')
    can('read', 'admin/products')
  }
  if (hasRole(ROLE_SUPERADMIN) || hasRole(ROLE_ADMIN)) {
    can('manage', 'admin/farms')
    can('manage', 'admin/depots')
    can('manage', 'admin/initiatives')
    can('manage', 'admin/users')
    can('read', 'admin/roles')
  }

  // admin backend: main navigation menu permissions
  if (hasRole(ROLE_SUPERADMIN)) {
    can('read', 'admin/menu/farms')
    can('read', 'admin/menu/depots')
    can('read', 'admin/menu/initiatives')
    can('read', 'admin/menu/users')
    can('read', 'admin/menu/goals')
    can('read', 'admin/menu/badges')
    can('read', 'admin/menu/roles')
    can('read', 'admin/menu/products')
    can('read', 'admin/menu/jobs')
  } else if (hasRole(ROLE_ADMIN)) {
    can('read', 'admin/menu/farms')
    can('read', 'admin/menu/depots')
    can('read', 'admin/menu/initiatives')
    can('read', 'admin/menu/users')
  }

  // job queue UI
  if (hasRole(ROLE_SUPERADMIN)) {
    can('read', 'arena')
  }

  // app
  if (hasRole(ROLE_USER) || hasRole(ROLE_ADMIN) || hasRole(ROLE_SUPERADMIN)) {
    can('create', 'autocomplete')
    can('create', 'geocoder')
    can('read', 'entries')

    can('read', 'farms', READABLE_FARM_ATTRIBUTES)
    can('read', 'farms', ['address'], { ownerships: userId })
    can('create', 'farms', WRITABLE_FARM_ATTRIBUTES)
    can(['update', 'delete'], 'farms', READABLE_FARM_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'depots', READABLE_DEPOT_ATTRIBUTES)
    can('read', 'depots', ['address'], { ownerships: userId })
    can('create', 'depots', WRITABLE_DEPOT_ATTRIBUTES)
    can(['update', 'delete'], 'depots', WRITABLE_DEPOT_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'initiatives', READABLE_INITIATIVE_ATTRIBUTES)
    can('read', 'initiatives', ['address'], { ownerships: userId })
    can('create', 'initiatives', WRITABLE_INITIATIVE_ATTRIBUTES)
    can(['update', 'delete'], 'initiatives', WRITABLE_INITIATIVE_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'products')
    can('read', 'goals')
    can('read', 'badges')
  } else {
    // guest
    can('create', 'autocomplete')
    can('create', 'geocoder')
    can('read', 'entries')
    can('read', 'farms', READABLE_FARM_ATTRIBUTES)
    can('read', 'depots', READABLE_DEPOT_ATTRIBUTES)
    can('read', 'initiatives', READABLE_INITIATIVE_ATTRIBUTES)
    can('read', 'products')
    can('read', 'goals')
    can('read', 'badges')
  }

  // login
  can('create', 'authentication')
  // confirm email
  can('create', 'authManagement')
  // sign up
  can('create', 'users')
  // access own user account
  can('patch', 'users', { id: userId })
  can('read', 'users', { id: userId })
  // submit entry contact form
  can('create', 'entrycontactmessage')

  return new Ability(rules, { subjectName })
}

const registeredConditions = ['ownerships']

const checkCondition = (condition) => {
  switch (condition) {
    case 'ownerships':
      return (resource, value) =>
        resource.properties.ownerships.some((o) => o.id === value.toString())
    default:
      return (resource, value) => resource[condition] === value
  }
}

const checkConditions = (resource, conditions) =>
  _.keys(conditions).every((name) =>
    checkCondition(name)(resource, conditions[name])
  )

export const authorize = async (ctx) => {
  const { method: action, service, path: serviceName } = ctx
  const ability = defineAbilities(ctx)

  const throwUnlessCan = (a, resource) => {
    if (ability.cannot(a, resource)) {
      throw new Forbidden(`You are not allowed to ${a} ${resource}.`)
    }
  }

  // check service permissions
  throwUnlessCan(action, serviceName)

  // allow collection requests (read, create)
  if (!ctx.id) {
    // check field permissions
    // TODO also implement field level conditions?
    const allowedFields = permittedFieldsOf(ability, action, serviceName)

    if (allowedFields.length > 0) {
      ctx.allowedFields = allowedFields
    }
    return ctx
  }

  // fetch the resource with required eager queries
  const allConditions = Object.assign(
    {},
    ...ability.rulesFor(action, serviceName).map((r) => r.conditions)
  )

  const eager = _.keys(allConditions).filter((name) =>
    registeredConditions.includes(name)
  )
  const resource = await service.get(ctx.id, {
    query: { $eager: `[${eager.join(',')}]` },
    provider: null,
  })

  // check resource rules that apply to the entire resource, not fields
  const resourceConditions = Object.assign(
    {},
    ...ability
      .rulesFor(action, serviceName)
      .filter((r) => !r.fields)
      .map((r) => r.conditions)
  )

  if (resourceConditions && !checkConditions(resource, resourceConditions)) {
    throw new Forbidden(
      `You are not allowed to ${action} ${resource.properties.type} ${resource.properties.id}.`
    )
  }

  // check field permissions (with possible field-level conditions)
  const allowedFields = permittedFieldsOf(ability, action, serviceName, {
    fieldsFrom: (rule) => {
      if (rule.conditions) {
        return checkConditions(ctx.id, resource, rule.conditions)
          ? rule.fields
          : []
      }
      return rule.fields
    },
  })

  if (allowedFields.length > 0) {
    ctx.allowedFields = allowedFields
    const forbiddenFields = _.keys(
      _.pickBy(ctx.data, (value, key) => allowedFields.includes(key))
    )
    if (forbiddenFields.length > 0) {
      throw new Forbidden(
        `You are not allowed to write fields ${forbiddenFields} of ${resource.type}`
      )
    }
  }

  return ctx
}

export const addAbilitiesToResponse = (ctx) => {
  ctx.result.abilities = defineAbilities(ctx).rules
}
<commit_msg>Update admin authorizations.<commit_after>import decode from 'jwt-decode'
import { Forbidden } from '@feathersjs/errors'
import { Ability, AbilityBuilder } from '@casl/ability'
import { permittedFieldsOf } from '@casl/ability/extra'
import _ from 'lodash'

Ability.addAlias('update', 'patch')
Ability.addAlias('read', ['get', 'find'])
Ability.addAlias('delete', 'remove')

// hardcoded roles that must match roles defined in database (for now)
const ROLE_USER = 'user'
const ROLE_ADMIN = 'admin'
const ROLE_SUPERADMIN = 'superadmin'

const subjectName = (subject) =>
  !subject || typeof subject === 'string' ? subject : subject.type()

const extractRolesFromJwtToken = (ctx) => {
  if (ctx.params.headers && ctx.params.headers.authorization) {
    return decode(ctx.params.headers.authorization).roles
  }
  if (ctx.result && ctx.result.accessToken) {
    return decode(ctx.result.accessToken).roles
  }
  return []
}

const defineAbilities = (ctx) => {
  const roles = extractRolesFromJwtToken(ctx)

  if (!ctx.params.user) {
    ctx.params.user = {
      id: -1,
      name: 'guest',
    }
  }

  const userId = ctx.params.user.id

  ctx.app.debug(
    roles.length > 0 ? roles.map((r) => r.name) : 'none',
    `authorized user ${userId} ${ctx.params.user.name} with roles`
  )

  const hasRole = (role) => roles.find((r) => r.name === role)

  const { rules, can } = AbilityBuilder.extract()

  const WRITABLE_DEPOT_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'farms',
    'deliveryDays',
  ]
  const READABLE_DEPOT_ATTRIBUTES = [
    ...WRITABLE_DEPOT_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  const WRITABLE_FARM_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'depots',
    'acceptsNewMembers',
    'foundedAtYear',
    'foundedAtMonth',
    'maximumMembers',
    'additionalProductInformation',
    'participation',
    'actsEcological',
    'economicalBehavior',
    'products',
    'badges',
  ]
  const READABLE_FARM_ATTRIBUTES = [
    ...WRITABLE_FARM_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  const WRITABLE_INITIATIVE_ATTRIBUTES = [
    'name',
    'postalcode',
    'city',
    'state',
    'country',
    'latitude',
    'longitude',
    'description',
    'url',
    'goals',
    'badges',
  ]
  const READABLE_INITIATIVE_ATTRIBUTES = [
    ...WRITABLE_INITIATIVE_ATTRIBUTES,
    'id',
    'type',
    'link',
    'createdAt',
    'updatedAt',
  ]

  // admin backend: API permissions
  if (hasRole(ROLE_SUPERADMIN)) {
    can('manage', 'admin/goals')
    can('manage', 'admin/badges')
    can('manage', 'admin/products')
  } else if (hasRole(ROLE_ADMIN)) {
    can('read', 'admin/goals')
    can('read', 'admin/badges')
    can('read', 'admin/products')
  }
  if (hasRole(ROLE_SUPERADMIN) || hasRole(ROLE_ADMIN)) {
    can('manage', 'admin/farms')
    can('manage', 'admin/depots')
    can('manage', 'admin/initiatives')
    can('manage', 'admin/users')
    can('read', 'admin/roles')
    can('read', 'admin/jobs')
  }

  // admin backend: main navigation menu permissions
  if (hasRole(ROLE_SUPERADMIN)) {
    can('read', 'admin/menu/farms')
    can('read', 'admin/menu/depots')
    can('read', 'admin/menu/initiatives')
    can('read', 'admin/menu/users')
    can('read', 'admin/menu/goals')
    can('read', 'admin/menu/badges')
    can('read', 'admin/menu/roles')
    can('read', 'admin/menu/products')
    can('read', 'admin/menu/jobs')
  } else if (hasRole(ROLE_ADMIN)) {
    can('read', 'admin/menu/farms')
    can('read', 'admin/menu/depots')
    can('read', 'admin/menu/initiatives')
    can('read', 'admin/menu/users')
  }

  // job queue UI
  if (hasRole(ROLE_SUPERADMIN)) {
    can('read', 'arena')
  }

  // app
  if (hasRole(ROLE_USER) || hasRole(ROLE_ADMIN) || hasRole(ROLE_SUPERADMIN)) {
    can('create', 'autocomplete')
    can('create', 'geocoder')
    can('read', 'entries')

    can('read', 'farms', READABLE_FARM_ATTRIBUTES)
    can('read', 'farms', ['address'], { ownerships: userId })
    can('create', 'farms', WRITABLE_FARM_ATTRIBUTES)
    can(['update', 'delete'], 'farms', READABLE_FARM_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'depots', READABLE_DEPOT_ATTRIBUTES)
    can('read', 'depots', ['address'], { ownerships: userId })
    can('create', 'depots', WRITABLE_DEPOT_ATTRIBUTES)
    can(['update', 'delete'], 'depots', WRITABLE_DEPOT_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'initiatives', READABLE_INITIATIVE_ATTRIBUTES)
    can('read', 'initiatives', ['address'], { ownerships: userId })
    can('create', 'initiatives', WRITABLE_INITIATIVE_ATTRIBUTES)
    can(['update', 'delete'], 'initiatives', WRITABLE_INITIATIVE_ATTRIBUTES, {
      ownerships: userId,
      active: true,
    })

    can('read', 'products')
    can('read', 'goals')
    can('read', 'badges')
  } else {
    // guest
    can('create', 'autocomplete')
    can('create', 'geocoder')
    can('read', 'entries')
    can('read', 'farms', READABLE_FARM_ATTRIBUTES)
    can('read', 'depots', READABLE_DEPOT_ATTRIBUTES)
    can('read', 'initiatives', READABLE_INITIATIVE_ATTRIBUTES)
    can('read', 'products')
    can('read', 'goals')
    can('read', 'badges')
  }

  // login
  can('create', 'authentication')
  // confirm email
  can('create', 'authManagement')
  // sign up
  can('create', 'users')
  // access own user account
  can('patch', 'users', { id: userId })
  can('read', 'users', { id: userId })
  // submit entry contact form
  can('create', 'entrycontactmessage')

  return new Ability(rules, { subjectName })
}

const registeredConditions = ['ownerships']

const checkCondition = (condition) => {
  switch (condition) {
    case 'ownerships':
      return (resource, value) =>
        resource.properties.ownerships.some((o) => o.id === value.toString())
    default:
      return (resource, value) => resource[condition] === value
  }
}

const checkConditions = (resource, conditions) =>
  _.keys(conditions).every((name) =>
    checkCondition(name)(resource, conditions[name])
  )

export const authorize = async (ctx) => {
  const { method: action, service, path: serviceName } = ctx
  const ability = defineAbilities(ctx)

  const throwUnlessCan = (a, resource) => {
    if (ability.cannot(a, resource)) {
      throw new Forbidden(`You are not allowed to ${a} ${resource}.`)
    }
  }

  // check service permissions
  throwUnlessCan(action, serviceName)

  // allow collection requests (read, create)
  if (!ctx.id) {
    // check field permissions
    // TODO also implement field level conditions?
    const allowedFields = permittedFieldsOf(ability, action, serviceName)

    if (allowedFields.length > 0) {
      ctx.allowedFields = allowedFields
    }
    return ctx
  }

  // fetch the resource with required eager queries
  const allConditions = Object.assign(
    {},
    ...ability.rulesFor(action, serviceName).map((r) => r.conditions)
  )

  const eager = _.keys(allConditions).filter((name) =>
    registeredConditions.includes(name)
  )
  const resource = await service.get(ctx.id, {
    query: { $eager: `[${eager.join(',')}]` },
    provider: null,
  })

  // check resource rules that apply to the entire resource, not fields
  const resourceConditions = Object.assign(
    {},
    ...ability
      .rulesFor(action, serviceName)
      .filter((r) => !r.fields)
      .map((r) => r.conditions)
  )

  if (resourceConditions && !checkConditions(resource, resourceConditions)) {
    throw new Forbidden(
      `You are not allowed to ${action} ${resource.properties.type} ${resource.properties.id}.`
    )
  }

  // check field permissions (with possible field-level conditions)
  const allowedFields = permittedFieldsOf(ability, action, serviceName, {
    fieldsFrom: (rule) => {
      if (rule.conditions) {
        return checkConditions(ctx.id, resource, rule.conditions)
          ? rule.fields
          : []
      }
      return rule.fields
    },
  })

  if (allowedFields.length > 0) {
    ctx.allowedFields = allowedFields
    const forbiddenFields = _.keys(
      _.pickBy(ctx.data, (value, key) => allowedFields.includes(key))
    )
    if (forbiddenFields.length > 0) {
      throw new Forbidden(
        `You are not allowed to write fields ${forbiddenFields} of ${resource.type}`
      )
    }
  }

  return ctx
}

export const addAbilitiesToResponse = (ctx) => {
  ctx.result.abilities = defineAbilities(ctx).rules
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import Response from 'ember-cli-mirage/response';
import { HOSTS } from './common';
import { logFrames, logEncode } from './data/logs';
import { generateDiff } from './factories/job-version';
import { generateTaskGroupFailures } from './factories/evaluation';
import { copy } from 'ember-copy';

export function findLeader(schema) {
  const agent = schema.agents.first();
  return `${agent.address}:${agent.tags.port}`;
}

export function filesForPath(allocFiles, filterPath) {
  return allocFiles.where(
    file =>
      (!filterPath || file.path.startsWith(filterPath)) &&
      file.path.length > filterPath.length &&
      !file.path.substr(filterPath.length + 1).includes('/')
  );
}

export default function() {
  this.timing = 0; // delay for each request, automatically set to 0 during testing

  this.logging = window.location.search.includes('mirage-logging=true');

  this.namespace = 'v1';
  this.trackRequests = Ember.testing;

  const nomadIndices = {}; // used for tracking blocking queries
  const server = this;
  const withBlockingSupport = function(fn) {
    return function(schema, request) {
      // Get the original response
      let { url } = request;
      url = url.replace(/index=\d+[&;]?/, '');
      const response = fn.apply(this, arguments);

      // Get and increment the appropriate index
      nomadIndices[url] || (nomadIndices[url] = 2);
      const index = nomadIndices[url];
      nomadIndices[url]++;

      // Annotate the response with the index
      if (response instanceof Response) {
        response.headers['X-Nomad-Index'] = index;
        return response;
      }
      return new Response(200, { 'x-nomad-index': index }, response);
    };
  };

  this.get(
    '/jobs',
    withBlockingSupport(function({ jobs }, { queryParams }) {
      const json = this.serialize(jobs.all());
      const namespace = queryParams.namespace || 'default';
      return json
        .filter(job =>
          namespace === 'default'
            ? !job.NamespaceID || job.NamespaceID === namespace
            : job.NamespaceID === namespace
        )
        .map(job => filterKeys(job, 'TaskGroups', 'NamespaceID'));
    })
  );

  this.post('/jobs', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');

    return okEmpty();
  });

  this.post('/jobs/parse', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.JobHCL)
      return new Response(400, {}, 'JobHCL is a required field on the request payload');
    if (!body.Canonicalize) return new Response(400, {}, 'Expected Canonicalize to be true');

    // Parse the name out of the first real line of HCL to match IDs in the new job record
    // Regex expectation:
    //   in:  job "job-name" {
    //   out: job-name
    const nameFromHCLBlock = /.+?"(.+?)"/;
    const jobName = body.JobHCL.trim()
      .split('\n')[0]
      .match(nameFromHCLBlock)[1];

    const job = server.create('job', { id: jobName });
    return new Response(200, {}, this.serialize(job));
  });

  this.post('/job/:id/plan', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');
    if (!body.Diff) return new Response(400, {}, 'Expected Diff to be true');

    const FailedTGAllocs = body.Job.Unschedulable && generateFailedTGAllocs(body.Job);

    return new Response(
      200,
      {},
      JSON.stringify({ FailedTGAllocs, Diff: generateDiff(req.params.id) })
    );
  });

  this.get(
    '/job/:id',
    withBlockingSupport(function({ jobs }, { params, queryParams }) {
      const job = jobs.all().models.find(job => {
        const jobIsDefault = !job.namespaceId || job.namespaceId === 'default';
        const qpIsDefault = !queryParams.namespace || queryParams.namespace === 'default';
        return (
          job.id === params.id &&
          (job.namespaceId === queryParams.namespace || (jobIsDefault && qpIsDefault))
        );
      });

      return job ? this.serialize(job) : new Response(404, {}, null);
    })
  );

  this.post('/job/:id', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');

    return okEmpty();
  });

  this.get(
    '/job/:id/summary',
    withBlockingSupport(function({ jobSummaries }, { params }) {
      return this.serialize(jobSummaries.findBy({ jobId: params.id }));
    })
  );

  this.get('/job/:id/allocations', function({ allocations }, { params }) {
    return this.serialize(allocations.where({ jobId: params.id }));
  });

  this.get('/job/:id/versions', function({ jobVersions }, { params }) {
    return this.serialize(jobVersions.where({ jobId: params.id }));
  });

  this.get('/job/:id/deployments', function({ deployments }, { params }) {
    return this.serialize(deployments.where({ jobId: params.id }));
  });

  this.get('/job/:id/deployment', function({ deployments }, { params }) {
    const deployment = deployments.where({ jobId: params.id }).models[0];
    return deployment ? this.serialize(deployment) : new Response(200, {}, 'null');
  });

  this.post('/job/:id/periodic/force', function(schema, { params }) {
    // Create the child job
    const parent = schema.jobs.find(params.id);

    // Use the server instead of the schema to leverage the job factory
    server.create('job', 'periodicChild', {
      parentId: parent.id,
      namespaceId: parent.namespaceId,
      namespace: parent.namespace,
      createAllocations: parent.createAllocations,
    });

    return okEmpty();
  });

  this.delete('/job/:id', function(schema, { params }) {
    const job = schema.jobs.find(params.id);
    job.update({ status: 'dead' });
    return new Response(204, {}, '');
  });

  this.get('/deployment/:id');
  this.post('/deployment/promote/:id', function() {
    return new Response(204, {}, '');
  });

  this.get('/job/:id/evaluations', function({ evaluations }, { params }) {
    return this.serialize(evaluations.where({ jobId: params.id }));
  });

  this.get('/evaluation/:id');

  this.get('/deployment/allocations/:id', function(schema, { params }) {
    const job = schema.jobs.find(schema.deployments.find(params.id).jobId);
    const allocations = schema.allocations.where({ jobId: job.id });

    return this.serialize(allocations.slice(0, 3));
  });

  this.get('/nodes', function({ nodes }) {
    const json = this.serialize(nodes.all());
    return json;
  });

  this.get('/node/:id');

  this.get('/node/:id/allocations', function({ allocations }, { params }) {
    return this.serialize(allocations.where({ nodeId: params.id }));
  });

  this.get('/allocations');

  this.get('/allocation/:id');

  this.post('/allocation/:id/stop', function() {
    return new Response(204, {}, '');
  });

  this.get('/namespaces', function({ namespaces }) {
    const records = namespaces.all();

    if (records.length) {
      return this.serialize(records);
    }

    return new Response(501, {}, null);
  });

  this.get('/namespace/:id', function({ namespaces }, { params }) {
    if (namespaces.all().length) {
      return this.serialize(namespaces.find(params.id));
    }

    return new Response(501, {}, null);
  });

  this.get('/agent/members', function({ agents, regions }) {
    const firstRegion = regions.first();
    return {
      ServerRegion: firstRegion ? firstRegion.id : null,
      Members: this.serialize(agents.all()),
    };
  });

  this.get('/status/leader', function(schema) {
    return JSON.stringify(findLeader(schema));
  });

  this.get('/acl/token/self', function({ tokens }, req) {
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    // Return the token if it exists
    if (tokenForSecret) {
      return this.serialize(tokenForSecret);
    }

    // Client error if it doesn't
    return new Response(400, {}, null);
  });

  this.get('/acl/token/:id', function({ tokens }, req) {
    const token = tokens.find(req.params.id);
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    // Return the token only if the request header matches the token
    // or the token is of type management
    if (token.secretId === secret || (tokenForSecret && tokenForSecret.type === 'management')) {
      return this.serialize(token);
    }

    // Return not authorized otherwise
    return new Response(403, {}, null);
  });

  this.get('/acl/policy/:id', function({ policies, tokens }, req) {
    const policy = policies.find(req.params.id);
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    if (req.params.id === 'anonymous') {
      if (policy) {
        return this.serialize(policy);
      } else {
        return new Response(404, {}, null);
      }
    }

    // Return the policy only if the token that matches the request header
    // includes the policy or if the token that matches the request header
    // is of type management
    if (
      tokenForSecret &&
      (tokenForSecret.policies.includes(policy) || tokenForSecret.type === 'management')
    ) {
      return this.serialize(policy);
    }

    // Return not authorized otherwise
    return new Response(403, {}, null);
  });

  this.get('/regions', function({ regions }) {
    return this.serialize(regions.all());
  });

  const clientAllocationStatsHandler = function({ clientAllocationStats }, { params }) {
    return this.serialize(clientAllocationStats.find(params.id));
  };

  const clientAllocationLog = function(server, { params, queryParams }) {
    const allocation = server.allocations.find(params.allocation_id);
    const tasks = allocation.taskStateIds.map(id => server.taskStates.find(id));

    if (!tasks.mapBy('name').includes(queryParams.task)) {
      return new Response(400, {}, 'must include task name');
    }

    if (queryParams.plain) {
      return logFrames.join('');
    }

    return logEncode(logFrames, logFrames.length - 1);
  };

  const clientAllocationFSLsHandler = function({ allocFiles }, { queryParams }) {
    // Ignore the task name at the beginning of the path
    const filterPath = queryParams.path.substr(queryParams.path.indexOf('/') + 1);
    const files = filesForPath(allocFiles, filterPath);
    return this.serialize(files);
  };

  const clientAllocationFSStatHandler = function({ allocFiles }, { queryParams }) {
    // Ignore the task name at the beginning of the path
    const filterPath = queryParams.path.substr(queryParams.path.indexOf('/') + 1);

    // Root path
    if (!filterPath) {
      return this.serialize({
        IsDir: true,
        ModTime: new Date(),
      });
    }

    // Either a file or a nested directory
    const file = allocFiles.where({ path: filterPath }).models[0];
    return this.serialize(file);
  };

  const clientAllocationCatHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;
    return file.body;
  };

  const clientAllocationStreamHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;

    // Pretender, and therefore Mirage, doesn't support streaming responses.
    return file.body;
  };

  const clientAllocationReadAtHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;
    return file.body.substr(queryParams.offset || 0, queryParams.limit);
  };

  const fileOrError = function(allocFiles, path, message = 'Operation not allowed on a directory') {
    // Ignore the task name at the beginning of the path
    const filterPath = path.substr(path.indexOf('/') + 1);

    // Root path
    if (!filterPath) {
      return [null, new Response(400, {}, message)];
    }

    const file = allocFiles.where({ path: filterPath }).models[0];
    if (file.isDir) {
      return [null, new Response(400, {}, message)];
    }

    return [file, null];
  };

  // Client requests are available on the server and the client
  this.put('/client/allocation/:id/restart', function() {
    return new Response(204, {}, '');
  });

  this.get('/client/allocation/:id/stats', clientAllocationStatsHandler);
  this.get('/client/fs/logs/:allocation_id', clientAllocationLog);

  this.get('/client/fs/ls/:allocation_id', clientAllocationFSLsHandler);
  this.get('/client/fs/stat/:allocation_id', clientAllocationFSStatHandler);
  this.get('/client/fs/cat/:allocation_id', clientAllocationCatHandler);
  this.get('/client/fs/stream/:allocation_id', clientAllocationStreamHandler);
  this.get('/client/fs/readat/:allocation_id', clientAllocationReadAtHandler);

  this.get('/client/stats', function({ clientStats }, { queryParams }) {
    const seed = faker.random.number(10);
    if (seed >= 8) {
      const stats = clientStats.find(queryParams.node_id);
      stats.update({
        timestamp: Date.now() * 1000000,
        CPUTicksConsumed: stats.CPUTicksConsumed + faker.random.number({ min: -10, max: 10 }),
      });
      return this.serialize(stats);
    } else {
      return new Response(500, {}, null);
    }
  });

  // TODO: in the future, this hack may be replaceable with dynamic host name
  // support in pretender: https://github.com/pretenderjs/pretender/issues/210
  HOSTS.forEach(host => {
    this.get(`http://${host}/v1/client/allocation/:id/stats`, clientAllocationStatsHandler);
    this.get(`http://${host}/v1/client/fs/logs/:allocation_id`, clientAllocationLog);

    this.get(`http://${host}/v1/client/fs/ls/:allocation_id`, clientAllocationFSLsHandler);
    this.get(`http://${host}/v1/client/stat/ls/:allocation_id`, clientAllocationFSStatHandler);
    this.get(`http://${host}/v1/client/fs/cat/:allocation_id`, clientAllocationCatHandler);
    this.get(`http://${host}/v1/client/fs/stream/:allocation_id`, clientAllocationStreamHandler);
    this.get(`http://${host}/v1/client/fs/readat/:allocation_id`, clientAllocationReadAtHandler);

    this.get(`http://${host}/v1/client/stats`, function({ clientStats }) {
      return this.serialize(clientStats.find(host));
    });
  });
}

function filterKeys(object, ...keys) {
  const clone = copy(object, true);

  keys.forEach(key => {
    delete clone[key];
  });

  return clone;
}

// An empty response but not a 204 No Content. This is still a valid JSON
// response that represents a payload with no worthwhile data.
function okEmpty() {
  return new Response(200, {}, '{}');
}

function generateFailedTGAllocs(job, taskGroups) {
  const taskGroupsFromSpec = job.TaskGroups && job.TaskGroups.mapBy('Name');

  let tgNames = ['tg-one', 'tg-two'];
  if (taskGroupsFromSpec && taskGroupsFromSpec.length) tgNames = taskGroupsFromSpec;
  if (taskGroups && taskGroups.length) tgNames = taskGroups;

  return tgNames.reduce((hash, tgName) => {
    hash[tgName] = generateTaskGroupFailures();
    return hash;
  }, {});
}
<commit_msg>Mock the eligibility endpoint in mirage<commit_after>import Ember from 'ember';
import Response from 'ember-cli-mirage/response';
import { HOSTS } from './common';
import { logFrames, logEncode } from './data/logs';
import { generateDiff } from './factories/job-version';
import { generateTaskGroupFailures } from './factories/evaluation';
import { copy } from 'ember-copy';

export function findLeader(schema) {
  const agent = schema.agents.first();
  return `${agent.address}:${agent.tags.port}`;
}

export function filesForPath(allocFiles, filterPath) {
  return allocFiles.where(
    file =>
      (!filterPath || file.path.startsWith(filterPath)) &&
      file.path.length > filterPath.length &&
      !file.path.substr(filterPath.length + 1).includes('/')
  );
}

export default function() {
  this.timing = 0; // delay for each request, automatically set to 0 during testing

  this.logging = window.location.search.includes('mirage-logging=true');

  this.namespace = 'v1';
  this.trackRequests = Ember.testing;

  const nomadIndices = {}; // used for tracking blocking queries
  const server = this;
  const withBlockingSupport = function(fn) {
    return function(schema, request) {
      // Get the original response
      let { url } = request;
      url = url.replace(/index=\d+[&;]?/, '');
      const response = fn.apply(this, arguments);

      // Get and increment the appropriate index
      nomadIndices[url] || (nomadIndices[url] = 2);
      const index = nomadIndices[url];
      nomadIndices[url]++;

      // Annotate the response with the index
      if (response instanceof Response) {
        response.headers['X-Nomad-Index'] = index;
        return response;
      }
      return new Response(200, { 'x-nomad-index': index }, response);
    };
  };

  this.get(
    '/jobs',
    withBlockingSupport(function({ jobs }, { queryParams }) {
      const json = this.serialize(jobs.all());
      const namespace = queryParams.namespace || 'default';
      return json
        .filter(job =>
          namespace === 'default'
            ? !job.NamespaceID || job.NamespaceID === namespace
            : job.NamespaceID === namespace
        )
        .map(job => filterKeys(job, 'TaskGroups', 'NamespaceID'));
    })
  );

  this.post('/jobs', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');

    return okEmpty();
  });

  this.post('/jobs/parse', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.JobHCL)
      return new Response(400, {}, 'JobHCL is a required field on the request payload');
    if (!body.Canonicalize) return new Response(400, {}, 'Expected Canonicalize to be true');

    // Parse the name out of the first real line of HCL to match IDs in the new job record
    // Regex expectation:
    //   in:  job "job-name" {
    //   out: job-name
    const nameFromHCLBlock = /.+?"(.+?)"/;
    const jobName = body.JobHCL.trim()
      .split('\n')[0]
      .match(nameFromHCLBlock)[1];

    const job = server.create('job', { id: jobName });
    return new Response(200, {}, this.serialize(job));
  });

  this.post('/job/:id/plan', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');
    if (!body.Diff) return new Response(400, {}, 'Expected Diff to be true');

    const FailedTGAllocs = body.Job.Unschedulable && generateFailedTGAllocs(body.Job);

    return new Response(
      200,
      {},
      JSON.stringify({ FailedTGAllocs, Diff: generateDiff(req.params.id) })
    );
  });

  this.get(
    '/job/:id',
    withBlockingSupport(function({ jobs }, { params, queryParams }) {
      const job = jobs.all().models.find(job => {
        const jobIsDefault = !job.namespaceId || job.namespaceId === 'default';
        const qpIsDefault = !queryParams.namespace || queryParams.namespace === 'default';
        return (
          job.id === params.id &&
          (job.namespaceId === queryParams.namespace || (jobIsDefault && qpIsDefault))
        );
      });

      return job ? this.serialize(job) : new Response(404, {}, null);
    })
  );

  this.post('/job/:id', function(schema, req) {
    const body = JSON.parse(req.requestBody);

    if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');

    return okEmpty();
  });

  this.get(
    '/job/:id/summary',
    withBlockingSupport(function({ jobSummaries }, { params }) {
      return this.serialize(jobSummaries.findBy({ jobId: params.id }));
    })
  );

  this.get('/job/:id/allocations', function({ allocations }, { params }) {
    return this.serialize(allocations.where({ jobId: params.id }));
  });

  this.get('/job/:id/versions', function({ jobVersions }, { params }) {
    return this.serialize(jobVersions.where({ jobId: params.id }));
  });

  this.get('/job/:id/deployments', function({ deployments }, { params }) {
    return this.serialize(deployments.where({ jobId: params.id }));
  });

  this.get('/job/:id/deployment', function({ deployments }, { params }) {
    const deployment = deployments.where({ jobId: params.id }).models[0];
    return deployment ? this.serialize(deployment) : new Response(200, {}, 'null');
  });

  this.post('/job/:id/periodic/force', function(schema, { params }) {
    // Create the child job
    const parent = schema.jobs.find(params.id);

    // Use the server instead of the schema to leverage the job factory
    server.create('job', 'periodicChild', {
      parentId: parent.id,
      namespaceId: parent.namespaceId,
      namespace: parent.namespace,
      createAllocations: parent.createAllocations,
    });

    return okEmpty();
  });

  this.delete('/job/:id', function(schema, { params }) {
    const job = schema.jobs.find(params.id);
    job.update({ status: 'dead' });
    return new Response(204, {}, '');
  });

  this.get('/deployment/:id');
  this.post('/deployment/promote/:id', function() {
    return new Response(204, {}, '');
  });

  this.get('/job/:id/evaluations', function({ evaluations }, { params }) {
    return this.serialize(evaluations.where({ jobId: params.id }));
  });

  this.get('/evaluation/:id');

  this.get('/deployment/allocations/:id', function(schema, { params }) {
    const job = schema.jobs.find(schema.deployments.find(params.id).jobId);
    const allocations = schema.allocations.where({ jobId: job.id });

    return this.serialize(allocations.slice(0, 3));
  });

  this.get('/nodes', function({ nodes }) {
    const json = this.serialize(nodes.all());
    return json;
  });

  this.get('/node/:id');

  this.get('/node/:id/allocations', function({ allocations }, { params }) {
    return this.serialize(allocations.where({ nodeId: params.id }));
  });

  this.post('/node/:id/eligibility', function({ nodes }, { params }) {
    return this.serialize(nodes.find({ id: params.id }));
  });

  this.get('/allocations');

  this.get('/allocation/:id');

  this.post('/allocation/:id/stop', function() {
    return new Response(204, {}, '');
  });

  this.get('/namespaces', function({ namespaces }) {
    const records = namespaces.all();

    if (records.length) {
      return this.serialize(records);
    }

    return new Response(501, {}, null);
  });

  this.get('/namespace/:id', function({ namespaces }, { params }) {
    if (namespaces.all().length) {
      return this.serialize(namespaces.find(params.id));
    }

    return new Response(501, {}, null);
  });

  this.get('/agent/members', function({ agents, regions }) {
    const firstRegion = regions.first();
    return {
      ServerRegion: firstRegion ? firstRegion.id : null,
      Members: this.serialize(agents.all()),
    };
  });

  this.get('/status/leader', function(schema) {
    return JSON.stringify(findLeader(schema));
  });

  this.get('/acl/token/self', function({ tokens }, req) {
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    // Return the token if it exists
    if (tokenForSecret) {
      return this.serialize(tokenForSecret);
    }

    // Client error if it doesn't
    return new Response(400, {}, null);
  });

  this.get('/acl/token/:id', function({ tokens }, req) {
    const token = tokens.find(req.params.id);
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    // Return the token only if the request header matches the token
    // or the token is of type management
    if (token.secretId === secret || (tokenForSecret && tokenForSecret.type === 'management')) {
      return this.serialize(token);
    }

    // Return not authorized otherwise
    return new Response(403, {}, null);
  });

  this.get('/acl/policy/:id', function({ policies, tokens }, req) {
    const policy = policies.find(req.params.id);
    const secret = req.requestHeaders['X-Nomad-Token'];
    const tokenForSecret = tokens.findBy({ secretId: secret });

    if (req.params.id === 'anonymous') {
      if (policy) {
        return this.serialize(policy);
      } else {
        return new Response(404, {}, null);
      }
    }

    // Return the policy only if the token that matches the request header
    // includes the policy or if the token that matches the request header
    // is of type management
    if (
      tokenForSecret &&
      (tokenForSecret.policies.includes(policy) || tokenForSecret.type === 'management')
    ) {
      return this.serialize(policy);
    }

    // Return not authorized otherwise
    return new Response(403, {}, null);
  });

  this.get('/regions', function({ regions }) {
    return this.serialize(regions.all());
  });

  const clientAllocationStatsHandler = function({ clientAllocationStats }, { params }) {
    return this.serialize(clientAllocationStats.find(params.id));
  };

  const clientAllocationLog = function(server, { params, queryParams }) {
    const allocation = server.allocations.find(params.allocation_id);
    const tasks = allocation.taskStateIds.map(id => server.taskStates.find(id));

    if (!tasks.mapBy('name').includes(queryParams.task)) {
      return new Response(400, {}, 'must include task name');
    }

    if (queryParams.plain) {
      return logFrames.join('');
    }

    return logEncode(logFrames, logFrames.length - 1);
  };

  const clientAllocationFSLsHandler = function({ allocFiles }, { queryParams }) {
    // Ignore the task name at the beginning of the path
    const filterPath = queryParams.path.substr(queryParams.path.indexOf('/') + 1);
    const files = filesForPath(allocFiles, filterPath);
    return this.serialize(files);
  };

  const clientAllocationFSStatHandler = function({ allocFiles }, { queryParams }) {
    // Ignore the task name at the beginning of the path
    const filterPath = queryParams.path.substr(queryParams.path.indexOf('/') + 1);

    // Root path
    if (!filterPath) {
      return this.serialize({
        IsDir: true,
        ModTime: new Date(),
      });
    }

    // Either a file or a nested directory
    const file = allocFiles.where({ path: filterPath }).models[0];
    return this.serialize(file);
  };

  const clientAllocationCatHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;
    return file.body;
  };

  const clientAllocationStreamHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;

    // Pretender, and therefore Mirage, doesn't support streaming responses.
    return file.body;
  };

  const clientAllocationReadAtHandler = function({ allocFiles }, { queryParams }) {
    const [file, err] = fileOrError(allocFiles, queryParams.path);

    if (err) return err;
    return file.body.substr(queryParams.offset || 0, queryParams.limit);
  };

  const fileOrError = function(allocFiles, path, message = 'Operation not allowed on a directory') {
    // Ignore the task name at the beginning of the path
    const filterPath = path.substr(path.indexOf('/') + 1);

    // Root path
    if (!filterPath) {
      return [null, new Response(400, {}, message)];
    }

    const file = allocFiles.where({ path: filterPath }).models[0];
    if (file.isDir) {
      return [null, new Response(400, {}, message)];
    }

    return [file, null];
  };

  // Client requests are available on the server and the client
  this.put('/client/allocation/:id/restart', function() {
    return new Response(204, {}, '');
  });

  this.get('/client/allocation/:id/stats', clientAllocationStatsHandler);
  this.get('/client/fs/logs/:allocation_id', clientAllocationLog);

  this.get('/client/fs/ls/:allocation_id', clientAllocationFSLsHandler);
  this.get('/client/fs/stat/:allocation_id', clientAllocationFSStatHandler);
  this.get('/client/fs/cat/:allocation_id', clientAllocationCatHandler);
  this.get('/client/fs/stream/:allocation_id', clientAllocationStreamHandler);
  this.get('/client/fs/readat/:allocation_id', clientAllocationReadAtHandler);

  this.get('/client/stats', function({ clientStats }, { queryParams }) {
    const seed = faker.random.number(10);
    if (seed >= 8) {
      const stats = clientStats.find(queryParams.node_id);
      stats.update({
        timestamp: Date.now() * 1000000,
        CPUTicksConsumed: stats.CPUTicksConsumed + faker.random.number({ min: -10, max: 10 }),
      });
      return this.serialize(stats);
    } else {
      return new Response(500, {}, null);
    }
  });

  // TODO: in the future, this hack may be replaceable with dynamic host name
  // support in pretender: https://github.com/pretenderjs/pretender/issues/210
  HOSTS.forEach(host => {
    this.get(`http://${host}/v1/client/allocation/:id/stats`, clientAllocationStatsHandler);
    this.get(`http://${host}/v1/client/fs/logs/:allocation_id`, clientAllocationLog);

    this.get(`http://${host}/v1/client/fs/ls/:allocation_id`, clientAllocationFSLsHandler);
    this.get(`http://${host}/v1/client/stat/ls/:allocation_id`, clientAllocationFSStatHandler);
    this.get(`http://${host}/v1/client/fs/cat/:allocation_id`, clientAllocationCatHandler);
    this.get(`http://${host}/v1/client/fs/stream/:allocation_id`, clientAllocationStreamHandler);
    this.get(`http://${host}/v1/client/fs/readat/:allocation_id`, clientAllocationReadAtHandler);

    this.get(`http://${host}/v1/client/stats`, function({ clientStats }) {
      return this.serialize(clientStats.find(host));
    });
  });
}

function filterKeys(object, ...keys) {
  const clone = copy(object, true);

  keys.forEach(key => {
    delete clone[key];
  });

  return clone;
}

// An empty response but not a 204 No Content. This is still a valid JSON
// response that represents a payload with no worthwhile data.
function okEmpty() {
  return new Response(200, {}, '{}');
}

function generateFailedTGAllocs(job, taskGroups) {
  const taskGroupsFromSpec = job.TaskGroups && job.TaskGroups.mapBy('Name');

  let tgNames = ['tg-one', 'tg-two'];
  if (taskGroupsFromSpec && taskGroupsFromSpec.length) tgNames = taskGroupsFromSpec;
  if (taskGroups && taskGroups.length) tgNames = taskGroups;

  return tgNames.reduce((hash, tgName) => {
    hash[tgName] = generateTaskGroupFailures();
    return hash;
  }, {});
}
<|endoftext|>