Source: bc_utilities.js

/* 

bc_utilities.js


commonly used routines:




function backupFile(filename);
make a copy of file with YYYYmmdd_HHMMSS.BAK

function writeUpdateFile(file_key,key);
	write current time to  file with name "last_<file_key>_updated.json" and to subobject given by key
	key is typically the name of the script that made the last update
	will preserve any pre-existing keys (so we have history of when each key was last updated)

	{	"last_updated": <time_now>,
		"<key>": {
			"last_updated": <time_now>
		}
	}

function initializeFromCSVPathWithKey(path,key);
if key is null, then load as array of rows

function initializeFromYAMLPath(path);

function deepCopy(originalObject) return a deep copy of the original object


function readJSONfromCWD(filePath)
	returns json from the given path, assuming path is within curren working directory

function readJSONfromAppDir(dirname,filePath)
	returns json from the given path, assuming path is__dirname (same dir a calling node script)


isUndefined(id)
isNull(id)
isUndefinedOrIsNull(id)

notUndefined(id)
notNull(id)
notUndefinedAndNotNull(id)






*/




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


const execFileSync = require('child_process').execFileSync;
const parse = require('csv-parse/lib/sync');
// read in srad.csv for year

const yaml = require('js-yaml');

/*-------------------------------------------------------------------------*/

function getDatetimeString(date_object) {

let month = (date_object.getMonth() + 1).toString().padStart(2, '0');
    let date = date_object.getDate().toString().padStart(2, '0');
    let hour = date_object.getHours().toString().padStart(2, '0');
    let minute = date_object.getMinutes().toString().padStart(2, '0');
    let seconds = date_object.getSeconds().toString().padStart(2, '0');

    let datetime_string = "" + date_object.getFullYear() + month + date + "_" + hour + minute + seconds;
    
    return datetime_string;

}

function backupFile(filename) {

    // make a copy of file with YYYYmmddhhMMSS.BAK appended to end of file name
    // and put copy into _backups directory in the files parent directory
    // eg backupFile("/dir1/dir2/myfile.json")
    // -> "/dir/dir2/_backups/myfile.json.202001311653.BAK"

    // backup file date based on last modification date of the file,
    // and NOT the current time that backup is called
    if (!fs.existsSync(filename)) {
        return;
    }

    let stats = fs.statSync(filename);
    let modification_time = stats.mtime;
    
   let time_string = getDatetimeString(modification_time);

    
    let backup_path = path.dirname(filename) + "/_backups";
    if (!fs.existsSync(backup_path)) {
        fs.mkdirSync(backup_path);
    }

    let backup_filename = backup_path + "/" + path.basename(filename) + "." + time_string + ".BAK";

    fs.copyFileSync(filename, backup_filename);

}
/*-------------------------------------------------------------------------*/

function writeUpdateFile(file_key, key) {
    /*
    write current time to  jekyll/_data/file with name "last_<file_key>_updated.json" and to subobject given by key
    key is typically the name of the script that made the last update
    will preserve any pre-existing keys (so we have history of when each key was last updated)

    {	"last_updated": <time_now>,
    	"<key>": {
    		"last_updated": <time_now>
    	}
    }

    */
    const file_name = "last_" + file_key + "_update.json";

    var update = {};
    if (fs.existsSync(file_name)) {
        update = JSON.parse(fs.readFileSync(file_name));
    }
    if (null != key) {
        if (typeof update[key] == "undefined") {
            update[key] = {};
        }
        update[key]['last_updated'] = new Date().toLocaleString();
    }
    update['last_updated'] = new Date().toLocaleString();
    fs.writeFileSync(__dirname + "/jekyll/_data/" + file_name, JSON.stringify(update, null, 5));

}




/*-------------------------------------------------------------------------*/

function initializeFromCSVPathWithKey(path, key) {

    // if key null, then load as rows

    if (!fs.existsSync(path)) {
        console.log("No CSV file at: " + path + "!!");
        process.exit(1);
    }

    let csv_string = fs.readFileSync(path);
    let csv = null;

    if (null == key) {
        csv = parse(csv_string, {
            'columns': true
        });
    }
    else {
        csv = parse(csv_string, {
            'columns': true,
            'objname': key
        });
    }

    return csv;
}

/*-------------------------------------------------------------------------*/

function initializeFromYAMLPath(path) {

    if (!fs.existsSync(path)) {
        console.log("No YAML file at: " + path + "!!");
        process.exit(1);
    }

    return (yaml.safeLoad(fs.readFileSync(path, 'utf8')));


}

/*-------------------------------------------------------------------------*/

function deepCopy(originalObject) {
    let strValue = JSON.stringify(originalObject);
    return JSON.parse(strValue);
}

/*-------------------------------------------------------------------------*/

function readJSON(filePath) {

    let data = fs.readFileSync(filePath);
    return JSON.parse(data);
}


function readJSONfromCWD(filePath) {

    // path.resolve(yourPath) === path.normalize(yourPath).replace( RegExp(path.sep+'$'), '' );
    let data = fs.readFileSync(path.resolve(filePath));
    return JSON.parse(data);
}

function readJSONfromAppDir(dirname, filePath) {
    // have to pass in __dirname from calling app

    let data = fs.readFileSync(path.join(dirname, filePath));

    return JSON.parse(data);

}

/*-------------------------------------------------------------------------*/

function isUndefined(id) {
    return ("undefined" == typeof id);
}

function isNull(id) {
    return (null == id);
}

function isUndefinedOrIsNull(id) {
    if (isUndefined(id)) return true;
    if (isNull(id)) return true;
    return false;
}

/*-------------------------------------------------------------------------*/

function notUndefined(id) {
    return ("undefined" != typeof id);
}

function notNull(id) {
    return (null != id);
}

function notUndefinedAndNotNull(id) {
    return (notUndefined(id) && notNull(id));
}

/*-------------------------------------------------------------------------*/
/**

returns true if parent, or parent[child], is undefined or null


*/

function isUndefinedOrIsNullParentAndChild(parent, child) {
    if (isUndefined(parent)) return true;
    if (isNull(parent)) return true;
    if (isUndefined(parent[child])) return true;
    if (isNull(parent[child])) return true;
    return false;
}

function notUndefinedAndNotNullParentAndChild(parent, child) {
    if (notUndefined(parent) && notNull(parent)) {
        return (notUndefined(parent[child]) && notNull(parent[child]));
    }
    return false;
}
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------

function isEmptyObject(theObject) {

    // returns true if:
    // - theObject is null or undefined
    // - theObject is a string of zero length, or contains only white space
    // - theObject is an array of zero length
    // - theObject has no keys
    // - theObject has keys, but key-value isEmptyObject

    // console.log("\n\n\n" + theObject);

    if (isUndefinedOrIsNull(theObject)) {
        // console.log("null or undefined"); 
        return true;
    }

    if (typeof theObject == "string") {
        return ((theObject.length == 0) || !(/\S/.test(theObject)));
    }

    if (typeof theObject == "object") {

        if (Array.isArray(theObject)) {
            return (theObject.length == 0);
        }

        if (Object.keys(theObject).length == 0) {
            // console.log("empty object"); 
            return true;
        }

        let empty_flag = true;
        Object.keys(theObject).forEach(function(key) {

            // console.log("Subkey: " + key);

            // if any of the subkeys are NOT empty, then object isn't empty
            if (!isEmptyObject(theObject[key])) {
                // console.log("Not Empty Subkey"); 
                empty_flag = false;
            }

        });

        // object must be empty
        if (empty_flag) {
            // console.log("object with empty keys"); 
        }
        return empty_flag;

    }

    // otherwise it is a number or Boolean or symbol or bigint or function
    // console.log("Not Empty");
    return false;

} // isEmptyObject
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------

function removeInvalidKeysFromObject(theObject) {

    // call this routine on each student in $scope.students before sending to eve/mongodb
    // to strip out all keys with null or empty values from the data structure
    // removeInvalids recursively removes undefined/nulls/empty/whitespace from object, subobjects, and subarrays
    // if object ends up "empty" (ie object has keys, but key-values are empty), then it deletes that object too

    //// Compact arrays with null entries; delete keys from objects with null value
    // https://stackoverflow.com/questions/18515254/recursively-remove-null-values-from-javascript-object
    function removeInvalids(obj) {

        let isArray = obj instanceof Array;

//         if (isArray) {
//             console.log(obj);
//             console.log("array length: " + obj.length);
//             console.log("keys length: " + Object.keys(obj).length);
//             console.log("values length: " + Object.values(obj).length);
//             console.log("\n\n");
//         }


        // iterate backwards through keys so we catch all members of array even after splicing
        for (let i = (Object.keys(obj).length - 1); i >= 0; i--) {

          let k = Object.keys(obj)[i];

          if (!k.includes("$")) {

               if (isEmptyObject(obj[k])) {
          
     //             if (obj[k] === undefined ||
     //                 obj[k] === null ||
     //                 obj[k] === '' ||
     //                 !(/\S/.test(obj[k]))
     //             ) {

                     isArray ? obj.splice(i, 1) : delete obj[k];

                 }
                 else if (typeof obj[k] == "object") {
                     removeInvalids(obj[k]);
                     if (isEmptyObject(obj[k])) {
                         delete obj[k];
                     }
                 }
           } // key doesn't include "$"
        } // next key
    } // removeNulls

    removeInvalids(theObject);

    return theObject;


} // removeInvalidKeysFromObject

function removeInvalidKeysFromObjectDebug(theObject) {

    // call this routine on each student in $scope.students before sending to eve/mongodb
    // to strip out all keys with null or empty values from the data structure
    // removeInvalids recursively removes undefined/nulls/empty/whitespace from object, subobjects, and subarrays
    // if object ends up "empty" (ie object has keys, but key-values are empty), then it deletes that object too

    //// Compact arrays with null entries; delete keys from objects with null value
    // https://stackoverflow.com/questions/18515254/recursively-remove-null-values-from-javascript-object

    var recurse_level = 0;

    function removeInvalids(obj) {

        recurse_level++;

        let spacer = "";
        for (var j = 0; j < recurse_level; j++) spacer += "	";

        var isArray = obj instanceof Array;
        // iterate backwards through keys so we catch all members of array even after splicing

        for (var i = (Object.keys(obj).length - 1); i >= 0; i--) {

            var k = Object.keys(obj)[i];


            console.log(spacer + recurse_level + " key " + i + " " + k);

            if (k == "academic_plans") {

                console.log("GOT TO ACADEMIC PLANS");
            }
            if (obj[k] === undefined ||
                obj[k] === null ||
                obj[k] === '' ||
                !(/\S/.test(obj[k]))
            ) {

                isArray ? obj.splice(i, 1) : delete obj[k];
                if (k == "discontinued" && obj.plan_code == "BIOLOGYPD" &&
                    obj["defense_date"] == "11/8/2019") {
                    console.log("discontinued - > " + obj[k] + " still has key " + Object.keys(obj).includes(k));
                }
            }
            else if (typeof obj[k] == "object") {
                if (k == "academic_plans") {
                    let academic_plans_flag = true;
                    console.log("RECURSING ON ACADEMIC PLANS " + Object.keys(obj[k]).length);
                }

                removeInvalids(obj[k]);
                if (isEmptyObject(obj[k])) {
                    delete obj[k];
                }
            }
        }

        recurse_level--;
    } // removeNulls

    //	let theObjectCopy = JSON.parse(JSON.stringify(theObject),null,"	");
    removeInvalids(theObject);

    return theObject;


} // removeInvalidKeysFromObject



// ------------------------------------------------------------------------

var _removeInvalidKeysFromStudent = function(student) {

    // call this routine on each student in $scope.students before sending to eve/mongodb
    // to strip out all keys with null or empty values from the data structure
    // removeInvalids recursively removes undefined/nulls/empty/whitespace from object, subobjects, and subarrays

    //// Compact arrays with null entries; delete keys from objects with null value
    // https://stackoverflow.com/questions/18515254/recursively-remove-null-values-from-javascript-object
    function removeInvalids(obj) {
        var isArray = obj instanceof Array;

 //        if (isArray) {
//             console.log(obj);
//             console.log("array length: " + obj.length);
//             console.log("keys length: " + Object.keys(obj).length);
//             console.log("values length: " + Object.values(obj).length);
//             console.log("\n\n");
//         }


        // iterate backwards through keys so we catch all members of array even after splicing
        for (var i = Object.keys(obj).length - 1; i >= 0; i--) {
            var k = Object.keys(obj)[i];

            if (!k.includes("$")) {

                 //  if (isArray && i == 1) { console.log(k + ": " + obj[k]); }

                 if ((obj[k] instanceof Array) && (0 == obj[k].length)) {
                     isArray ? obj.splice(i, 1) : delete obj[k];
                 }
                 else if ((obj[k] instanceof Object) && (0 == Object.keys(obj[k]).length)) {

                     isArray ? obj.splice(i, 1) : delete obj[k];
                 }
                 else if (typeof obj[k] == "string" && obj[k].length == 0) {
                     isArray ? obj.splice(i, 1) : delete obj[k];
                 }
                 else if (obj[k] === undefined ||
                     obj[k] === null ||
                     obj[k] === "" // is === "" equivalent to === '' ? because "" fields seem to be getting through...
                     ||
                     !(/\S/.test(obj[k]))
                 ) {
                     isArray ? obj.splice(i, 1) : delete obj[k];
                 }
                 else if (typeof obj[k] == "object") {
                     removeInvalids(obj[k]);
                     // object may now be empty, in which case delete it
                     if (0 == Object.keys(obj[k]).length) {
                         isArray ? obj.splice(i, 1) : delete obj[k];
                     }
                 }
             } // key doesn't contain "$"
        } // next key

    } // removeInvalids

    removeInvalids(student);

    return student;

};
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------

// TEST OF REMOVE INVALID OBJECTS

//const undefinedObject;
const nullObject = null;
const trueObject = true;
const falseObject = false;

const emptyObject = {};
const emptyArray = [];
const emptyString = "";
const spaceString = "	";
const spaceTabReturnString = "  \t  \n  ";


const objectWithEmptyObjects = {
    "ok": {
        "name": "Tom",
        "foo": "bar"
    },
    "no_missing": {
        "name": "Tom",
        "foo": "bar",
        "missingString": emptyString,
        "missingArray": emptyArray,
        "missingObject": emptyObject
    },
    "ok_array": ["element1", "element2", "element3"],
    "no_missing_array": ["element1", nullObject, emptyString, spaceString, "element2"],
    "array1": nullObject,
    "array2": emptyArray,
    "array3": [nullObject, emptyString, spaceString, emptyObject],
    "array4": [trueObject, falseObject],
    "array5": ["foo", emptyObject],

    "objnull": nullObject,
    "obj0": emptyObject,
    "obj1": {
        "obj2": {
            "foo": "bar",
            "obj3": {
                "nullObject": nullObject,
                "emptyString": emptyString,
                "emptyArray": emptyArray,
                "foo": "bar"
            },
            "obj4": {
                "nullObject": nullObject,
                "emptyString": emptyString,
                "emptyArray": emptyArray,
            }

        }
    }

};

const onlyValidObjects = {
    "ok": {
        "name": "Tom",
        "foo": "bar"
    },
    "no_missing": {
        "name": "Tom",
        "foo": "bar"
    },
    "ok_array": ["element1", "element2", "element3"],
    "no_missing_array": ["element1", "element2"],
    "array4": [true, false],
    "array5": ["foo"],

    "obj1": {
        "obj2": {
            "foo": "bar",
            "obj3": {
                "foo": "bar"
            }
        }
    }

};


// console.log(["foo",{}].length);

const onlyValidObjects_string = JSON.stringify(onlyValidObjects, null, "	");
const emptyObjects_string = JSON.stringify(objectWithEmptyObjects, null, "	");
const strippedObject = removeInvalidKeysFromObject(objectWithEmptyObjects);
const strippedStudent = _removeInvalidKeysFromStudent(objectWithEmptyObjects);

const strippedObject_string = JSON.stringify(strippedObject, null, "	");
const strippedStudent_string = JSON.stringify(strippedStudent, null, "	");

if (onlyValidObjects_string != strippedObject_string) {
    console.log("FAILED removeInvalidKeysFromObject");
    console.log(emptyObjects_string);
    console.log(strippedObject_string);
    process.exit(1);
}
if (onlyValidObjects_string != strippedStudent_string) {
    console.log("FAILED _removeInvalidKeysFromStudent");
    process.exit(1);
}
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------

var fsuid_regex = /employeeID: (.+)\n/;
var email_regex = /mail: (.+)\n/;
var dept_regex = /department: ([^\()]+)/;
var photo_regex = /url: (.+)\n/;
var given_regex = /givenName: (.+)\n/;

var CN_regex = /dn: CN=([a-z0-9]+)\,/;




let ldap_cache = {};

// check how old the ldap cache is:
// if > 1 week, then delete it. (new one will be created with first look up)
// otherwise read it in...

if (fs.existsSync(__dirname + "/_ldap_cache.json")) {

     if (fs.existsSync(__dirname + "/_ldap_cache_started.json")) {
     
          let ldap_cache_started = readJSON(__dirname + "/_ldap_cache_started.json");
          let time_now = Date.now();
          if (time_now - ldap_cache_started.timestamp < (7* 86400000)) {
               ldap_cache = readJSON(__dirname + "/_ldap_cache.json");
          }
          else {
               fs.unlinkSync(__dirname + "/_ldap_cache.json");
          }
     }    
    
}


function getLDAPInfo(username, password, emplid) {
    /* 

    given emplid, looks up fsuid via ldap
     return either fsuid or null

    example ldap lookup by emplid

    ```
    ldapsearch -L -L -L -h fsu-dc-1.fsu.edu -D <fsuid@fsu.edu> -w <password> -b 'dc=fsu,dc=edu' -s sub 'fsuEduEMPLID=200625657' employeeID
    ```

    returns:

    ```
    dn: CN=mps18bg,OU=PEOPLE,DC=fsu,DC=edu
    employeeID: mps18bg

    # refldap://mscloud.fsu.edu/DC=mscloud,DC=fsu,DC=edu

    # refldap://fsu.edu/CN=Configuration,DC=fsu,DC=edu

    # refldap://ForestDnsZones.fsu.edu/DC=ForestDnsZones,DC=fsu,DC=edu

    # refldap://DomainDnsZones.fsu.edu/DC=DomainDnsZones,DC=fsu,DC=edu
    ```

    ```
    ldapsearch -L -L -L -h fsu-dc-1.fsu.edu -D thoupt@fsu.edu -w <password> -b 'dc=fsu,dc=edu' -s sub 'fsuEduEMPLID=000110293' employeeID mail department
    ```

    returns
    ```
    dn: CN=ebangi,OU=PEOPLE,DC=fsu,DC=edu
    department: Biological Science (BIOLOGY) (074000)
    employeeID: ebangi
    mail: ebangi@fsu.edu

    # refldap://mscloud.fsu.edu/DC=mscloud,DC=fsu,DC=edu

    # refldap://fsu.edu/CN=Configuration,DC=fsu,DC=edu

    # refldap://ForestDnsZones.fsu.edu/DC=ForestDnsZones,DC=fsu,DC=edu

    # refldap://DomainDnsZones.fsu.edu/DC=DomainDnsZones,DC=fsu,DC=edu
    ```
    =========

    ldapsearch -L -L -L -h fsu-dc-1.fsu.edu -D thoupt@fsu.edu -w <password> -b 'dc=fsu,dc=edu' -s sub 'fsuEduEMPLID=200625657'
    dn: CN=mps18bg,OU=PEOPLE,DC=fsu,DC=edu
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: user
    cn: mps18bg
    sn: Schumm
    title: Graduate Assistant in Teaching
    description: Mr Matthew Philip Schumm
    physicalDeliveryOfficeName: 2065 KIN MC 4295
    givenName: Matthew
    distinguishedName: CN=mps18bg,OU=PEOPLE,DC=fsu,DC=edu
    instanceType: 4
    whenCreated: 20181222134358.0Z
    whenChanged: 20200117210152.0Z
    displayName: Matthew Schumm
    uSNCreated: 5715116
    memberOf: CN=ug-FSU-Students-All,OU=Special Purpose Groups,OU=FSU Special Purp
     ose,DC=fsu,DC=edu
    memberOf: CN=ug-FSU-Employees-All,OU=Special Purpose Groups,OU=FSU Special Pur
     pose,DC=fsu,DC=edu
    uSNChanged: 937369625
    department: Biological Science (BIOLOGY) 074000
    company: FSUID = MPS18BG
    proxyAddresses: x500:/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOH
     F23SPDLT)/cn=Recipients/cn=db18e29ac3bf49079470b9367270d591-mps18bg
    proxyAddresses: SMTP:mschumm@fsu.edu
    proxyAddresses: smtp:mps18bg@fsu.edu
    proxyAddresses: X400:C=US;A= ;P=Florida State Un;O=OLYMPUS;S=Schumm;G=Matthew;
    proxyAddresses: smtp:mps18bg@fsu.mail.onmicrosoft.com
    targetAddress: SMTP:mps18bg@fsu.mail.onmicrosoft.com
    extensionAttribute6: EMPLOYEE
    mailNickname: mps18bg
    extensionAttribute15: EMPLOYEE,FACULTY,GRAD,OPS,STUDENT
    employeeType: Active
    name: mps18bg
    objectGUID:: MuiR+9frw0SyHX2PZXafBw==
    userAccountControl: 66048
    badPwdCount: 0
    codePage: 0
    countryCode: 0
    employeeID: mps18bg
    badPasswordTime: 0
    lastLogoff: 0
    lastLogon: 132108785589384411
    pwdLastSet: 132172645540782866
    primaryGroupID: 513
    objectSid:: AQUAAAAAAAUVAAAAxrtQevGzEnEH5TsrEj8KAA==
    accountExpires: 0
    logonCount: 1
    sAMAccountName: mps18bg
    sAMAccountType: 805306368
    showInAddressBook: CN=Default Global Address List,CN=All Global Address Lists,
     CN=Address Lists Container,CN=Florida State University,CN=Microsoft Exchange,
     CN=Services,CN=Configuration,DC=fsu,DC=edu
    legacyExchangeDN: /o=Florida State University/ou=Exchange Administrative Group
      (FYDIBOHF23SPDLT)/cn=Recipients/cn=00bbfe6de33948c7943f08acc93a75a2
    userPrincipalName: mps18bg@fsu.edu
    url: http://images.its.fsu.edu/fsucardimages/small/01PWHPQG.JPG
    objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=fsu,DC=edu
    dSCorePropagationData: 20190814195154.0Z
    dSCorePropagationData: 20190814192313.0Z
    dSCorePropagationData: 20190718213640.0Z
    dSCorePropagationData: 20190709173138.0Z
    dSCorePropagationData: 16010714223649.0Z
    lastLogonTimestamp: 132180643450412373
    textEncodedORAddress: X400:C=US;A= ;P=Florida State Un;O=OLYMPUS;S=Schumm;G=Ma
     tthew;
    mail: mschumm@fsu.edu
    manager: CN=dokamoto,OU=PEOPLE,DC=fsu,DC=edu
    middleName: Philip
    msExchPoliciesExcluded: {26491cfc-9e50-4857-861b-0cb8df22b5d7}
    msExchVersion: 88218628259840
    fsuEduAffiliation: EMPLOYEE,FACULTY,GRAD,OPS,STUDENT
    fsuEduAffiliations: STUDENT
    fsuEduAffiliations: FACULTY
    fsuEduAffiliations: EMPLOYEE
    fsuEduAffiliations: OPS
    fsuEduAffiliations: GRAD
    msExchUMDtmfMap: emailAddress:6724866
    msExchUMDtmfMap: lastNameFirstName:7248666288439
    msExchUMDtmfMap: firstNameLastName:6288439724866
    msExchRecipientDisplayType: -2147483642
    msExchRecipientTypeDetails: 2147483648
    msExchUserHoldPolicies: 98E9BABD09A04bcf8455A58C2AA74182
    fsuEduEMPLID: 200625657
    fsuEduAppRoles: CS_ADMN_STDT_CNT
    msExchRemoteRecipientType: 1

    # refldap://mscloud.fsu.edu/DC=mscloud,DC=fsu,DC=edu

    # refldap://fsu.edu/CN=Configuration,DC=fsu,DC=edu

    # refldap://ForestDnsZones.fsu.edu/DC=ForestDnsZones,DC=fsu,DC=edu

    # refldap://DomainDnsZones.fsu.edu/DC=DomainDnsZones,DC=fsu,DC=edu

    =========

     ldapsearch -L -L -L -h fsu-dc-1.fsu.edu -D thoupt@fsu.edu -w <password> -b 'dc=fsu,dc=edu' -s sub 'fsuEduEMPLID=000024253'
    dn: CN=thoupt,OU=PEOPLE,DC=fsu,DC=edu
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: user
    cn: thoupt
    sn: Houpt
    title: Professor   9 Mo SAL
    description: Thomas A Houpt
    physicalDeliveryOfficeName: 3010 MSB:1 MC 4300:1
    telephoneNumber: 850/644-4424
    facsimileTelephoneNumber: --
    givenName: Thomas
    distinguishedName: CN=thoupt,OU=PEOPLE,DC=fsu,DC=edu
    instanceType: 4
    whenCreated: 20110316215523.0Z
    whenChanged: 20200331005852.0Z
    displayName: Thomas Houpt
    uSNCreated: 134843
    memberOf: CN=ug-FSU-Employees-All,OU=Special Purpose Groups,OU=FSU Special Pur
     pose,DC=fsu,DC=edu
    memberOf: CN=dl-neuro-ctp,OU=Neuro,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    memberOf: CN=dl-sfn-neuro,OU=Neuro,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    memberOf: CN=dl-everyone-neuro,OU=Neuro,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    memberOf: CN=dl-neuro-gfs,OU=Neuro,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    memberOf: CN=dl-neuro-faculty,OU=Neuro,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    memberOf: CN=ug-TEC-CurrentInstructors,OU=Technology Enhanced Classrooms,OU=Co
     mputing Technology Support Managed,OU=FSU-Dept-Groups,DC=fsu,DC=edu
    uSNChanged: 1513368190
    department: Biological Science (BIOLOGY) (074000)
    company: FSUID = thoupt
    deliverAndRedirect: TRUE
    proxyAddresses: SMTP:houpt@neuro.fsu.edu
    proxyAddresses: smtp:thoupt@fsu.edu
    proxyAddresses: x500:/o=Florida State University/ou=Exchange Administrative Gr
     oup (FYDIBOHF23SPDLT)/cn=Recipients/cn=thoupt4fc
    proxyAddresses: smtp:thoupt@fsu.mail.onmicrosoft.com
    proxyAddresses: SIP:thoupt@fsu.edu
    proxyAddresses: X500:/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOH
     F23SPDLT)/cn=Recipients/cn=5b056572065147838b6c5ab0b85f2d09-thoupt
    proxyAddresses: smtp:thoupt@admin.fsu.edu
    proxyAddresses: smtp:houpt@fsu.edu
    proxyAddresses: smtp:thoupt@mailer.fsu.edu
    proxyAddresses: smtp:thomas.houpt@fsu.edu
    proxyAddresses: smtp:thomas.a.houpt@fsu.edu
    proxyAddresses: X400:C=US;A= ;P=Florida State Un;O=OLYMPUS;S=Houpt;G=Thomas;
    targetAddress: SMTP:thoupt@fsu.mail.onmicrosoft.com
    extensionAttribute6: EMPLOYEE
    directReports: CN=smstanley,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=sbebus,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jsun4,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=mcortez,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=sgong2,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=cchang2,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jac18gf,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dstorace,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=knl13,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=rvincis,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ebangi,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=wwb15,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dokamoto,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=xwang18,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=sks14b,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=pfraser,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jmc14r,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=arassweiler,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=rcc08e,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=rlh15b,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=qyin,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=smiller,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dm16g,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ytian3,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jfeng3,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kgarner2,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=smccoy,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=clg11g,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dhoule,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=sburgess,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=smarks,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=sr13r,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=aj09f,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=mcharreldennis,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=elemmon,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=amw08e,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ssteppan,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jtravis,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=acw05d,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=eduval,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=gerickson,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ptrombley,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jwulff,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=tmiller,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=llyons,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kahughes,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=amast,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kchodyla,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dfadool,OU=Graduate School,OU=Computing Technology Support M
     anaged,OU=FSU-Dept-Accounts,DC=fsu,DC=edu
    directReports: CN=athistle,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=fzhu,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kmjones2,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=hbass,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=bchadwick,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=hyu,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=mstroupe,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=drokyta,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ktaylor,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jhdennis,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=ysu,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=jfadool,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kmcginnis,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=nunderwood,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=kdixon,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=awinn,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=beh10d,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=hcui2,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=bjg04c,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=htang,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dgilbert,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=astuy,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=dlevitan,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=bwashburn,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=pfajer,OU=Institute of Molecular Biophysics,OU=FSU-Dept-Acco
     unts,DC=fsu,DC=edu
    directReports: CN=yhuang,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=vcarter,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=binouye,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=egranger,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=pchase,OU=PEOPLE,DC=fsu,DC=edu
    directReports: CN=slenhert,OU=PEOPLE,DC=fsu,DC=edu
    mailNickname: thoupt
    extensionAttribute11:: QmlvbG9naWNhbCBTY2llbmNlIChCSU9MT0dZKSA=
    extensionAttribute12: 74000
    extensionAttribute13: BIOLOGICAL SCIENCE
    extensionAttribute15: 9_10MONTH,EMPLOYEE,FACULTY
    employeeType: Active
    name: thoupt
    objectGUID:: 0wEUiomZTEuoJ6MOxsGK2w==
    userAccountControl: 66048
    badPwdCount: 0
    codePage: 0
    countryCode: 0
    employeeID: thoupt
    badPasswordTime: 132293872471850639
    lastLogoff: 0
    lastLogon: 132293873040455889
    pwdLastSet: 132224505785793976
    primaryGroupID: 513
    objectSid:: AQUAAAAAAAUVAAAAxrtQevGzEnEH5Tsrr1MCAA==
    accountExpires: 9223372036854775807
    logonCount: 381
    sAMAccountName: thoupt
    sAMAccountType: 805306368
    showInAddressBook: CN=Default Global Address List,CN=All Global Address Lists,
     CN=Address Lists Container,CN=Florida State University,CN=Microsoft Exchange,
     CN=Services,CN=Configuration,DC=fsu,DC=edu
    legacyExchangeDN: /o=Florida State University/ou=External (FYDIBOHF25SPDLT)/cn
     =Recipients/cn=72ed2cb119d94ddfa76761a152fc06d3
    userPrincipalName: thoupt@fsu.edu
    lockoutTime: 0
    url: http://images.its.fsu.edu/fsucardimages/small/EYO793C3.JPG
    objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=fsu,DC=edu
    dSCorePropagationData: 20190814195208.0Z
    dSCorePropagationData: 20190814192329.0Z
    dSCorePropagationData: 20190718213701.0Z
    dSCorePropagationData: 20190709173154.0Z
    dSCorePropagationData: 16010714223649.0Z
    lastLogonTimestamp: 132300899324520618
    textEncodedORAddress: X400:C=US;A= ;P=Florida State Un;O=OLYMPUS;S=Houpt;G=Tho
     mas;
    mail: houpt@neuro.fsu.edu
    manager: CN=shuckaba,OU=Arts and Sciences,OU=FSU-Dept-Accounts,DC=fsu,DC=edu
    msExchALObjectVersion: 126
    msExchUserAccountControl: 0
    msExchMailboxGuid:: jVNvfEByRE28mpKEw8cTug==
    msExchPoliciesExcluded: {26491cfc-9e50-4857-861b-0cb8df22b5d7}
    msExchVersion: 44220983382016
    fsuEduAffiliation: 9_10MONTH,EMPLOYEE,FACULTY
    fsuEduAffiliations: FACULTY
    fsuEduAffiliations: 9_10MONTH
    fsuEduAffiliations: EMPLOYEE
    msExchUMDtmfMap: reversedPhone:4244446058
    msExchUMDtmfMap: emailAddress:46878
    msExchUMDtmfMap: lastNameFirstName:46878846627
    msExchUMDtmfMap: firstNameLastName:84662746878
    msExchRecipientDisplayType: -2147483642
    msExchRecipientTypeDetails: 2147483648
    msExchUserHoldPolicies: 98E9BABD09A04bcf8455A58C2AA74182Unlimit
    sharePointProfileStatus: Yes
    fsuEduEMPLID: 000024253
    fsuEduAppRoles: CS_ADMN_STDT_CNT
    fsuEduAppRoles: CS_CC_STUVIEW
    msExchTextMessagingState: 302120705
    msExchTextMessagingState: 16842751
    msExchWhenMailboxCreated: 20111222220740.0Z
    msExchRemoteRecipientType: 4

    # refldap://mscloud.fsu.edu/DC=mscloud,DC=fsu,DC=edu

    # refldap://fsu.edu/CN=Configuration,DC=fsu,DC=edu

    # refldap://ForestDnsZones.fsu.edu/DC=ForestDnsZones,DC=fsu,DC=edu

    # refldap://DomainDnsZones.fsu.edu/DC=DomainDnsZones,DC=fsu,DC=edu




     */

    if (typeof ldap_cache[emplid] != "undefined") {

        return ldap_cache[emplid];
    }

    let ldap_args = [
        '-L', '-L', '-L',
        '-h', 'fsu-dc-1.fsu.edu',
        '-D', username + '@fsu.edu', //doesn't need to be in quotes if using execFileSync
        '-w', password, '-b',
        'dc=fsu,dc=edu', //doesn't need to be in quotes if using execFileSync
        '-s', 'sub',
        'fsuEduEMPLID=' + emplid.toString().padStart(9, '0'), //doesn't need to be in quotes if using execFileSync
        'employeeID',
        'mail',
        'department',
        'url',
        'givenName'
    ];


    var ldap_results = execFileSync("ldapsearch", ldap_args, []); // execFile

    // console.log(ldap_results.toString('utf8'));

    if (ldap_results.toString('utf8').match(fsuid_regex) == null) {
        console.log(emplid + ": no fsuid found!");
        //so use emplid
        return emplid;

    }

    var fsuid_match = ldap_results.toString('utf8').match(fsuid_regex);
    var email_match = ldap_results.toString('utf8').match(email_regex);
    var dept_match = ldap_results.toString('utf8').match(dept_regex);
    var photo_match = ldap_results.toString('utf8').match(photo_regex);
    var given_match = ldap_results.toString('utf8').match(given_regex);


    var fsuid = (fsuid_match != null) ? fsuid_match[1].trim() : null;
    var email = (email_match != null) ? email_match[1].trim() : null;
    var dept = (dept_match != null) ? dept_match[1].trim() : null;
    var photo_url = (photo_match != null) ? photo_match[1].trim() : null;
    var given = (given_match != null) ? given_match[1].trim() : null;


    // sometimes employeeID is set to emplid, not fsuid...
    // so find from first line, "dn: CN=<fsuid>"

    if (parseInt(fsuid, 10) == emplid) {
        fsuid = ldap_results.toString('utf8').match(CN_regex)[1];
    }

    //console.log("found " + emplid.toString().padStart(9,'0') + ": " + fsuid);

    // cache so we don't have to look up again
    var ldap_info = {
        "fsuid": fsuid,
        "email": email,
        "department": dept,
        "photo_url": photo_url,
        "givenName": given
    };
    
    ldap_cache[emplid] = ldap_info;

     // before update ldap_cache on disk, make sure we have a created date field
     if (!fs.existsSync(__dirname + "/_ldap_cache_started.json")) {
           let time_now = Date.now();
          let ldap_cache_started = {"timestamp" : time_now };
          fs.writeFileSync(__dirname + "/_ldap_cache_started.json", JSON.stringify(ldap_cache_started));
     }
    fs.writeFileSync(__dirname + "/_ldap_cache.json", JSON.stringify(ldap_cache));


    return ldap_info;

}

// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------



module.exports = {

    initializeFromCSVPathWithKey: initializeFromCSVPathWithKey,
    initializeFromYAMLPath: initializeFromYAMLPath,
    writeUpdateFile: writeUpdateFile,
    getDatetimeString,
    backupFile: backupFile,

    deepCopy: deepCopy,

    readJSON: readJSON,
    readJSONfromCWD: readJSONfromCWD,
    readJSONfromAppDir: readJSONfromAppDir,

    isUndefined: isUndefined,
    isNull: isNull,
    isUndefinedOrIsNull: isUndefinedOrIsNull,

    isUndefinedOrIsNullParentAndChild,
    notUndefinedAndNotNullParentAndChild,

    notUndefined: notUndefined,
    notNull: notNull,
    notUndefinedAndNotNull: notUndefinedAndNotNull,

    isEmptyObject: isEmptyObject,
    removeInvalidKeysFromObject: removeInvalidKeysFromObject,
    removeInvalidKeysFromObjectDebug: removeInvalidKeysFromObjectDebug,

    getLDAPInfo: getLDAPInfo

};