Skip to content
Snippets Groups Projects
Select Git revision
  • 6eaaa77c365f80c03ad9742646b8a1dab534ff74
  • master default protected
  • v1.2.12
  • ctc2019
  • v1.1.11
  • v1.1.10
  • v1.1.8
  • v1.1.4
  • v1.0.9
  • v1.0.3
  • v1.0.2
11 results

server.js

Blame
  • user avatar
    ac_cx authored
    ea36cab6
    History
    server.js 26.60 KiB
    var express = require('express')
    var app = express()
    var path=require('path');
    const IS_NODE = typeof module !== 'undefined' && module.exports;
    
    //------------------- parse command line ------------------------------
    
    var gPI;
    var ROOT;
    var awRoot; // path to autowrangler files (if enabled)
    var PORT=3000;
    // secure can be turned off on command line, but default is on
    var bSecure=true;
    
    var argv = require('minimist')(process.argv.slice(2));
    
    if (1!=argv._.length) {
        ljsLog.error('USAGE: node server.js [--port=PORT] [--pi=piName] [--secure=true] [--log=/path/to/log.txt] [--awRoot=/path/to/awFiles] /path/to/files_LEVER/');
        return;
    }
    
    ROOT = argv._[0];
    // git rid of trailing slashes -- these mess up concatenation in middleware below
    if ('/'===ROOT[ROOT.length-1] || '\\'===ROOT[ROOT.length-1] )
        ROOT=ROOT.slice(0,ROOT.length-1);
    awRoot=argv.awRoot;
    
    global.leverRoot=ROOT;
    global.logFile=argv.log;
    
    // init logger after we set the root and log file, so we get file logs
    const LJS_LOG=require('./leverjs/ljsLog.js');
    const ljsLog=new LJS_LOG(true);
    
    
    if (undefined!==argv.port) {
        PORT=argv.port;
    }
    if (false==argv.secure || "false"==argv.secure) {
        ljsLog.log(' --> security DISABLED!')
        bSecure=false;
    }
    else {
        ljsLog.log('jwt security enabled : bSecure='+bSecure);
    }
    gPI=argv.pi;
    if (undefined==gPI || null==gPI)
        gPI='';
    else
        gPI=gPI.toLowerCase();
    ljsLog.log('gPI set to : '+gPI);
    
    const config=require('./config.js');
    
    // kick off a dbupdate
    function initRoot()
    {
        ljsLog.log('initiating dbUpdate');
        const spawn = require('child_process').spawn;
        var updateProc=spawn('node', ['leverjs/dbUpdate.js',ROOT]);
        var bUpdate=true;
        updateProc.on('close', (code) => {
            ljsLog.log('update complete. calling optimize');
            var optimizeProc=spawn('node', ['leverjs/dbOptimize.js',ROOT,"--quiet"]);
            
            optimizeProc.stdout.on('data', (data) => {
                ljsLog.log(`optimizeProc: ${data}`);
            });
            optimizeProc.on('close', (code) => {
                ljsLog.log('optimize completed with code '+code);
            });
        });
        updateProc.stdout.on('data', (data) => {
            ljsLog.log(`updateProc: ${data}`);
        });
    }
    initRoot();
        
    //--------------- auth0 security -------------------
    //
    var checkJwt;
    if (bSecure) {
        const express = require('express');
        const app = express();
        const jwt = require('express-jwt');
        const jwksRsa = require('jwks-rsa');
    
        // Authentication middleware. When used, the
        // access token must exist and be verified against
        // the Auth0 JSON Web Key Set
        checkJwt =  jwt({
                // Dynamically provide a signing key
                // based on the kid in the header and 
                // the signing keys provided by the JWKS endpoint.
                secret: jwksRsa.expressJwtSecret({
                    cache: true,
                    rateLimit: true,
                    jwksRequestsPerMinute: 5,
                    jwksUri: config.jwksUri
                }),
            
                // Validate the audience and the issuer.
                aud: config.aud,
                issuer: config.issuer,
                algorithms: ['RS256']
            });
    }
    else {
        checkJwt=function(res,req,next){
            next();
       } 
    }
    //--------------- auth0 end -------------------
    
    
    function checkLWT(req,res,next)
    {
        const jwt = require('jsonwebtoken');
        const fs=require('fs');
        const path=require('path');
        var token=req.headers.authorization;
        
        if (false===bSecure) {
            return next();        
        }
    
        if (undefined===token) {
            // no token. user needs to login via auth0 or send jwt directly
            return res.status(401).send('login required');
        }
    
        if (0===token.indexOf("Bearer")) {
            // then this originates via auth0
            // first, make sure jwt is valid
            checkJwt(req,res,function(){
                // check config override
                // reload config -- allows dynamic reconfig of access control
                const path=require('path');
                var configPath=path.join(process.cwd(),'config.js');
                delete require.cache[configPath]
                var config=require(configPath);
                var pi;
                if (undefined!=req.user) {
                // next check authorization (i.e. pi matches)
                    pi=req.user['https://leverjs.net/pi'];
                }
                if (undefined===req.user || null==pi || undefined==pi) {
                    return res.status(401).send('invalid user');
                }
                pi=pi.toLowerCase();
                if (pi===gPI || pi===config.defaultPI.toLowerCase()) {
                    next();
                    return;
                }
                // convert *'s to /'s in path
                var targetPath=req.path.replace(/\*/g,'\/');;
    
                for (var i=0;i<config.otherPI.length;i++) {
                    if (targetPath.indexOf(config.otherPI[i][0])>=0 && 
                            pi===config.otherPI[i][1].toLocaleLowerCase()) {
                        next();
                        return;
                    }
                }
                // else if we get here then the authorization failed 
                var userID=req.user['https://leverjs.net/email'];
                res.status(403).send('leverServer: authorization denied for user='+userID
                    +' with pi='+pi);
                ljsLog.warn('user '+userID+' not authorized: pi='+pi);
            });
            return;
        }
    
        // if we get here, it should be a jwt from e.g. matlab
        try {
            publicCert = fs.readFileSync('./digitalKeys/leverjsPublicKey.pem','utf-8');  // get public key        
        }
        catch(err){
            ljsLog.error('LWT: public key file not found '+err.message);
            res.status(401).send('no key file');
            return;
        }
        // use our public key file to verify this token
        jwt.verify(token, publicCert, function(err, decoded) {
          //ljsLog.log(JSON.stringify(decoded)) //
          if (err) {
            // token did not verify
            res.status(401).send('invalid token: '+err);
            return;
          }
          // if we got here, token was a-ok
          req.user=decoded;
          next();
        });
        
    } // checkLWT
    
    
    function checkLocalOrLWT(req, res, next)
    {
        if ( req.ip === '127.0.0.1' || req.ip === '::1' )
        {
            next();
        }
        else
        {
            checkLWT(req,res, next);
        }
    } // checkLocalOrLWT
    
    app.param('LEVER', function (req, res, next, leverFile) {
        
        req.ROOT=ROOT;
        req.awRoot=awRoot;
        req.params.LEVER=decodeURI(req.params.LEVER);
        req.params.LEVER=req.params.LEVER.replace(/\*/g,'\/');
        req.leverFile=path.join(req.ROOT,req.params.LEVER);
        
        next();
      });
    
    // called by split,undo,resegAll,resegNext,resegPause
    function prepareCommand(req,res,command,params)
    {
        const process=require('./leverjs/process.js');    
        var leverFile=req.leverFile;
        var user='NA';
        if (undefined!==req.user)
            user=req.user['https://leverjs.net/email'];
        else {
            var osUser=require('os').userInfo().username;
            username='server:'+osUser;    
        }
        var leverCommand={leverFile:leverFile, command:command, time:req.params.time, 
            params:params, user:user};
        var editID=process.addToQ(leverCommand);
        res.send(JSON.stringify(editID));
    } // prepareCommand
    
    // --------------------- routing table ----------------------------------------
    
    var bodyParser = require('body-parser');
    var jsonParser = bodyParser.json({limit:'1gb'});
    var rawParser = bodyParser.raw({type:'*/*',limit:'1gb'});
    
    app.use(express.static('./leverjs'));
    
    
    app.get('*/stats',function(req,res){
        const path=require('path');
        var target=path.join(ROOT,path.dirname(req.path));
        const helper=require('./leverjs/serverHelper');
        helper.getStats(target,res);
    });
    
    app.get('/:LEVER/editStats/:tMax',function(req,res) {
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var tMax=req.params.tMax;
        leverDB.getEditStats(tMax,function(editStats){
            res.send(editStats);
        });
    });
    
    app.get('/log',function(req,res) { 
        if (undefined===global.logFile) {
            res.send('sorry, logging not enabled');
            return;
        }  
        // else
        res.setHeader('Content-Language','en-us');
        const path=require('path');
        var logFile=path.resolve(global.logFile);
        res.sendFile(logFile,{},function(err){
            if (err) {
                ljsLog.error('app.get log file: '+err);
            }
        });
    
    });
    
    app.get('/info',function(req,res) {
        res.send(JSON.stringify({leverjs: 'server'}));
    });
    
    
    app.get('/API',function(req,res) {
        const helper=require('./leverjs/serverHelper');
        var html=helper.getAPI();
        
        res.send(html);
    
    });
    
    app.get('/secure', checkLWT, function(req,res){         
        ljsLog.log('secure')
        res.send("no, you're secure");
       
    });
    
    app.get('/hiFriend', function(req,res){         
        ljsLog.log('hifriend')
        res.send('hi'); 
    });
    
    app.get('/:LEVER/trackList/:tMin',function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var tMin=req.params.tMin;
        leverDB.trackList(tMin,function(trackList){
            res.send(trackList);
        });
    })
    
    app.post('/:LEVER/clone/:destFile',checkLWT,function(req,res) {
        const cl=require('./leverjs/cloneLever.js');
        const path=require('path');
        var leverFile=req.leverFile;
        var destFile=req.params.destFile;
        destFile=decodeURI(destFile);
        destFile=destFile.replace(/\*/g,'\/');
        destFile=path.join(req.ROOT,destFile);
        
        cl.cloneLever(leverFile,destFile,function(){
            res.send('done');
        });
    });
    
    app.post('/:LEVER/updateSurface/:cellID',checkLWT, jsonParser,function(req,res) {
        var command='updateSurface';
        var params={cellID:req.params.cellID, surface:req.body};
        prepareCommand(req,res,command,params);
    });
    
    app.get('/:LEVER/trackOptions/:cellID',function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var cellID=req.params.cellID;
        leverDB.getTrackOptions(cellID,function(trackOptions){
            res.send(trackOptions);
        });
    })
    
    app.get('/:LEVER/cellPhenotypes/:cellID',function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var cellID=req.params.cellID;
        leverDB.getCellPhenotypes(cellID,function(cellPhenotypes){
            res.send(cellPhenotypes);
        });
    })
    app.get('/:LEVER/vertices/:cellID',function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var cellID=req.params.cellID;
        leverDB.getVertices(cellID,function(vertices){
            res.send(vertices);
            leverDB.close();
        });
    })
    
    
    app.post('/:LEVER/createCells/',checkLWT, jsonParser,function(req,res) {
        var command='createCells';
        var params={cells:req.body};
        prepareCommand(req,res,command,params);  
    });
    
    app.post('/allRenderParams/',checkLWT, jsonParser, function(req,res) {
        var renderParams=req.body;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB;
        leverDB.allRenderParams(ROOT,renderParams);
        res.send('ok');
    });
    
    app.post('/:LEVER/killProcess/',checkLWT, function(req,res) {
        const process=require('./leverjs/process.js');
        process.pauseOrKillAll(true,req.leverFile);
        res.send('ok');
    });
    
    
    app.post('/pauseProcessAll/',checkLWT, function(req,res) {
        const process=require('./leverjs/process.js');
        process.pauseOrKillAll(false);
        res.send('ok');
    });
    
    app.post('/killProcessAll/',checkLWT, function(req,res) {
        const process=require('./leverjs/process.js');
        process.pauseOrKillAll(true);
        res.send('ok');
    });
    
        
    app.get('/processQ', function(req,res) {
            const process=require('./leverjs/process.js');
            const path=require('path');
            var rootStd=path.resolve(ROOT); // '/' not '\\'
            var Q=process.getQ(function(Q) {
                // remove root from each
                for (var i=0;i<Q.inProcessQ.length;i++) {
                    var lf=path.resolve(Q.inProcessQ[i].leverFile);
                    if (lf.indexOf(rootStd)>=0)
                        Q.inProcessQ[i].leverFile=Q.inProcessQ[i].leverFile.substring(ROOT.length+1);
                }
                for (var i=0;i<Q.toProcessQ.length;i++) {
                    var lf=path.resolve(Q.toProcessQ[i].leverFile);
                    if (lf.indexOf(rootStd)>=0)
                        Q.toProcessQ[i].leverFile=Q.toProcessQ[i].leverFile.substring(ROOT.length+1);
                }
    
                res.send(JSON.stringify(Q))
            });
    });
    
    app.post('/:LEVER/split/:cellID/:K/:time/:checkValue', checkLWT,function(req,res) {
        var command='split';
        var params={cellID:req.params.cellID, K:req.params.K, checkValue:req.params.checkValue};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/merge/:rgCellIDs/:time/:checkValue', checkLWT,function(req,res) {
        var command='merge';
        var params={rgCellIDs:req.params.rgCellIDs,checkValue:req.params.checkValue};    
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/mergeTracks/:rgTrackIDs/:time', checkLWT,function(req,res) {
        var command='mergeTracks';
        var params={rgTrackIDs:req.params.rgTrackIDs};    
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/changeTrackID/:cellID/:newTrackID/:oldTrackID/:time/', checkLWT,function(req,res) {
        var command='changeTrackID';
        var params={cellID:req.params.cellID, newTrackID:req.params.newTrackID, oldTrackID:req.params.oldTrackID};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/addMitosis/:child1/:child2/:time', checkLWT,  function(req,res) {
        var command='addMitosis';
        var params={child1:req.params.child1, child2:req.params.child2};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/removeMitosis/:child_cellID', checkLWT, function(req,res) {
        var command='removeMitosis';
        var params={child_cellID:req.params.child_cellID};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/cellPhenotype/:cellID/:phenotype/:bPhenotype/:time', checkLWT, function(req,res) {
        var command='cellPhenotype';
        var params={cellID:req.params.cellID,phenotype:req.params.phenotype,bPhenotype:req.params.bPhenotype};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/changeTrackColor/:trackID/:newColor', checkLWT, function(req,res) {
        var command='changeTrackColor';
        // we took off the # to keep the URI valid...put it back here.
        var newColor='#'+req.params.newColor;
        var params={trackID:req.params.trackID, newColor:newColor};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/undo/:undoEditID/:time/:undoCommand', checkLWT,function(req,res) {
        var command='undo';
        var params={undoEditID:req.params.undoEditID,undoCommand:req.params.undoCommand};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/resegAll/:time', checkLWT,function(req,res) {
        var command='resegAll';
        var params={};
        prepareCommand(req,res,command,params);    
    });
    
    app.post('/:LEVER/resegNext/:time', checkLWT,function(req,res) {
        var command='resegNext';
        var params={};
        prepareCommand(req,res,command,params);    
    });
    
    app.post('/:LEVER/resegPause/:time', checkLWT,function(req,res) {    
        // this goes straight into the commandTable -- no processQ
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var leverCommand={leverFile:leverFile,command:"resegPause",time:req.params.time};
        leverDB.addCommandToDB(leverCommand,function(){
            leverDB.close();
        });
        res.send('OK');
    });
    
    app.post('/:LEVER/eraseAll', checkLWT,function(req,res) {
        var command='eraseAll';
        var params={};
        prepareCommand(req,res,command,params);    
    });
    
    app.post('/experimentCommand',checkLWT,jsonParser,function(req,res) {
        const expHelper=require('./leverjs/experimentHelper.js');
        var leverExpCommand=req.body;
        var leverFileList=req.body.leverFileList;
        const path=require('path');
        for (var i=0;i<leverFileList.length;i++) {
            leverFileList[i]=leverFileList[i].replace(/\*/g,'\/');
            leverFileList[i]=path.join(ROOT,leverFileList[i]);
        }
        expHelper.experimentExecCmd(leverFileList,leverExpCommand,function(code,szStatus){
            if (undefined==code)
                res.status(200).send("OK");
            else 
                res.status(code).send(szStatus);
        })
    });
    
    app.post('/:LEVER/removeFromExtFamily/:trackID', checkLWT, function(req,res) {
        var command='removeFromExtFamily';
        var params={trackID:req.params.trackID};
        prepareCommand(req,res,command,params);
    });
    
    app.get('/:LEVER/addToExtFamilies/:trackID/:time', function(req,res) {
        console.log('got it!')
    });
    
    
    app.post('/:LEVER/addToExtFamilies/:trackID/:time', checkLWT, function(req,res) {
        var command='addToExtFamily';
        var params={trackID:req.params.trackID};
        prepareCommand(req,res,command,params);
    });
    
    
    app.post('/:LEVER/addAllMitoses', checkLWT,  function(req,res) {
        var command='addAllMitoses';
        var params={};
        prepareCommand(req,res,command,params);
    });
    
    app.post('/:LEVER/CONSTANTS/', checkLWT, jsonParser, function(req,res) {
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var CONSTANTS=req.body;
        leverDB.setConstants(CONSTANTS,function() {
            leverDB.close();
        });
        res.send('ok');
    });
    
    
    app.get('/:LEVER/trackFeatures/:trackID', function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        var trackID=JSON.parse(req.params.trackID);
        leverDB.getTrackFeatures(trackID,function(trackFeatures) {
            res.send(trackFeatures);
            leverDB.close();
        });
    })
    
    app.get('/:LEVER/algorithms', function(req,res) {
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        leverDB.getAlgorithms(function(err,algorithms) {
            res.send(algorithms);
        });
    });
    
    app.get('/:LEVER/featureExtrema', function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        leverDB.getConstants(function(err,CONSTANTS) {
            leverDB.getFeatureExtrema(CONSTANTS,function(rgMinMax) {
                res.send(rgMinMax);
            });
        });
      });
    
    app.get('/:LEVER/constants', function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        leverDB.getConstants(function(err,CONSTANTS) {
            // tag on autowrangler status
            if (err) {
                res.send(err.message);
                return;
            }
            CONSTANTS.ui.autoWrangler=(undefined!==awRoot);
            res.send(CONSTANTS);
            leverDB.close();
        });
      });
    
    app.get('/editStatus/:editID', function(req,res) {
        const process=require('./leverjs/process.js');    
        var editID=JSON.parse(req.params.editID);
        var status=process.getStatus(editID);
        if ('idle'===status)
            res.send(status); // idle
        else
            res.status(202).send(status); // busy
    });
    
    app.get('/algorithms', function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.getAvailableAlgorithms(req,res)
    });
    
    
    app.post('/:LEVER/addAlgorithm/', checkLWT, jsonParser, function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.setAlgorithmInDB(req,res);
    });
    
    app.post('/:LEVER/removeAlgorithm/:algName', checkLWT, function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.removeAlgorithmFromDB(req,res,false);
    });
    
    app.post('/:LEVER/removeAllAlgorithms/', checkLWT, function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.removeAlgorithmFromDB(req,res,true);
    });
    
    app.get('/:LEVER/cellPixels/:cellID', function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.getCellPixels(req,res);
    });
    
    app.get('/:LEVER/cellCentroids/:time', function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.getCellCentroids(req,res);
    });
    
    app.get('/:LEVER/cellSurface/:time', function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.getCellCentroids(req,res,true);
    });
    
    app.get('/:LEVER/editList', function(req,res) {
        const helper=require('./leverjs/serverHelper.js');
        helper.getEditList(req,res);
    });
    
    app.get('/:LEVER/colorTable', function(req,res){
        const helper=require('./leverjs/serverHelper.js');
        helper.getColorTable(req,res); 
    });
    
    app.get('/:LEVER/image/:time/p_:pack/l_:mrLevel/v_:mrChunk', function(req,res){
        const helper=require('./leverjs/serverHelper.js');
        helper.getImage(req,res);
    });
    
    app.get('/:LEVER/image/:time', function(req,res){
        const helper=require('./leverjs/serverHelper.js');
        helper.getImage(req,res);
    });
    
    app.get('/:LEVER/image/:time/:channel', function(req,res){
        const helper=require('./leverjs/serverHelper.js');
        helper.getImage(req,res);
    });
    
    app.get(['/:LEVER/cells/:time/c_:channelList','/:LEVER/cellsAW/:time/c_:channelList/:awid'],
         function(req,res){
            const helper=require('./leverjs/serverHelper.js');
            helper.getCells(req,res);
    });
    
    app.get(['/:LEVER/cells/:time','/:LEVER/cellsAW/:time/:awid'],function(req,res){
        const helper=require('./leverjs/serverHelper.js');
        helper.getCells(req,res);
    });
    
    app.get('/:LEVER/families', function(req,res){
        
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        leverDB.readLineage(function(leverStruct) {            
            res.send(leverStruct);        
            leverDB.close();
        });  
    
    });
    
    app.get('/:LEVER/lineage', function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB(leverFile);
        leverDB.readLineage(function(leverStruct) {            
            res.send(leverStruct);        
            leverDB.close();
        });  
    });
    
    
    app.get('/:LEVER/awParams', function(req,res){
        var leverFile=req.leverFile;
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB();
        leverDB.awParams(awRoot,leverFile,function(params) {
            res.send(params);
        });
       
    });
    
    app.get('*lever/', function(req,res){
        const LeverDB=require('./leverjs/lever.js');
        const path=require('path');    
        var targetPath=req.path;
        if (undefined!=req.headers.ljsroot)
            targetPath=path.join(req.headers.ljsroot,targetPath);
        targetPath=targetPath.replace(/\\/g,'/');
        if ('/' !== targetPath[targetPath.length-1])
            targetPath=targetPath+'/';
        var rgPath=targetPath.split('/');
        var extPath='';
        for (var n=0;n<rgPath.length-2; n++)
            extPath=path.join(extPath,rgPath[n]);
        require('./leverjs/lever.js');
        var leverDB=new LeverDB();
        leverDB.leverGetFileList(ROOT,function(leverFileList){
            res.send(leverFileList);
        }, extPath)
    });
    
    
    app.post('/loadfig/:fignum', rawParser, checkLocalOrLWT, function (req, res)
    {
        const helper = require('./leverjs/serverHelper.js');
    
        // Figure number must be an integer
        var fignum = parseInt(req.params.fignum);
        if ( fignum === undefined || isNaN(fignum) || fignum > 20 )
        {
            res.status(400).send('Invalid figure number');
            return;
        }
    
        helper.loadFigData(ROOT, req.body,fignum,function ()
        {
            res.status(200).send({response: 'Figure ' + fignum + ' loaded!'});
        });
    });
    
    app.get('/showfig/:fignum', function (req, res)
    {
        if ( undefined !== req.params.fignum )
            res.redirect('../ImageWindow.html?'+'_fig'+req.params.fignum+'.LEVER');
    });
    
    // serve the dataset :LEVER javascript / client page
    app.get('/:LEVER/lever', function (req, res) {
        const LeverDB=require('./leverjs/lever.js');
        var leverDB=new LeverDB();
        var leverFile=decodeURI(req.params.LEVER);
        leverFile = path.join(ROOT,leverFile);
        leverDB.leverRead(leverFile,0,ROOT,function(err,leverStruct){
            if(err) {
                res.status(400).send(JSON.stringify(err));
            }
            else {
                res.send(JSON.stringify(leverStruct));
            }
        });
    });
    
    // serve the dataset :LEVER javascript / client page
    app.get('/:LEVER(/*)?', function (req, res) {
        const path=require('path');
        if (undefined===req.params.LEVER) {
            res.redirect('ExperimentWindow.html?');
        }
        else {      
            if (path.extname(req.params.LEVER)!='LEVER') 
                res.redirect('ExperimentWindow.html?'+req.params.LEVER);
            else 
                res.redirect('ImageWindow.html?'+req.params.LEVER);
        }
    })
    
    // serve the root javascript / client page
    app.get('/', function (req, res) {
      //res.send('Hello World!')
      res.redirect('ExperimentWindow.html?');
    })
    
    // this middleware will not allow the request to go beyond it
    app.use(function(req, res, next) {
        // try to parse this as a subfolder with depth > 1 (/:LEVER above handles depth 1)
        // NOTE express js does not do absolute paths with reverse proxy correctly
        // so if we get /a/b/c we need to redirect to ../../experimentwindow
        // since ./experimentwindow drops the proxy path
        // build the relative path
        const path=require('path');
        var reqPath;
        if (undefined!==req.headers.ljsroot)
            reqPath=path.join(req.headers.ljsroot,req.path);
        else
            reqPath=req.path;
        reqPath=reqPath.replace(/\\/g,'/');
        var nfolders = ( req.path.match(/\//g) || []).length - 1; // counts '/' in req.path
        var redirectPath='ExperimentWindow.html?'+req.path;
        for (var i=0;i<nfolders;i++) {
            redirectPath='../'+redirectPath;
        }
        res.redirect(redirectPath);
      });
    
    // need to define error handling middleware last
    // this handles login failurs
    app.use(function (err, req, res, next) {
          if (err.name === 'UnauthorizedError' || err.name==='JsonWebTokenError') {
            res.status(401).send('leverServer: invalid credentials');
          }
    });
    
    // this starts the server
    app.listen(PORT, function () {
      ljsLog.log('leverjsServer: listening on port: ' + PORT + ' serving folder: '+ROOT);
    })