outlinerserver.js

var myVersion = "0.5.2", myProductName = "outlinerserver";
const fs = require ("fs");
const request = require ("request");
const utils = require ("daveutils");
const daveappserver = require ("daveappserver");
const daveappserver = require ("./lib/daveappserver.js");
var config = {
  fnameUserDatabase: "data/users.json",
  flDaytonaPing: false
  port: process.env.PORT || 1824,
  websocketPort: 1826,
  usersFolder: "data/users/",
  fnamePrefs: "prefs.json", //each user's prefs file
  fnameStats: "data/stats.json"
  };
var stats = {
  productName: myProductName,
  version: myVersion
  };
//user database -- 2/5/23 by DW
  var flUserDatabaseChanged = false;
  var userDatabase = {
    theUsers: {
      }
    }
  function getUserDatabase (callback) {
    fs.readFile (config.fnameUserDatabase, function (err, jsontext) {
      if (err) {
        callback (err);
        }
      else {
        try {
          var ct = 0;
          userDatabase = JSON.parse (jsontext);
          for (x in userDatabase.theUsers) {
            ct++
            }
          console.log ("getUserDatabase: " + ct + " users.");
          callback (undefined);
          }
        catch (err) {
          callback (err);
          }
        }
      });
    }
  function userDatabaseChanged () {
    flUserDatabaseChanged = true;
    }
  function isUserInDatabase (emailAddress, callback) {
    Changes
      3/8/23; 12:54:42 PM by DW
        Needed to be rewritten to iterate over the object, not index into it.
    var flfound = false;
    emailAddress = utils.stringLower (emailAddress); //3/8/23 by DW
    for (var x in userDatabase.theUsers) {
      if (!flfound) {
        if (utils.stringLower (x) == emailAddress) {
          callback (true, userDatabase.theUsers [x]);
          flfound = true;
          }
        }
      }
    if (!flfound) {
      callback (false);
      }
    const theUser = userDatabase.theUsers [emailAddress];
    if (theUser === undefined) {
      callback (false);
      }
    else {
      theUser.ctPageReads++;
      theUser.whenPageRead = new Date ();
      userDatabaseChanged ();
      callback (true, theUser);
      }
    }
  function addEmailToUserInDatabase (screenname, emailAddress, magicString, flNewUser, callback) {
    emailAddress = utils.stringLower (emailAddress); //3/8/23 by DW
    isUserInDatabase (emailAddress, function (flInDatabase, theUser) {
      if (flNewUser) {
        if (flInDatabase) {
          const message = "Can't create the user \"" + screenname + "\" because there already is a user with that email address."
          callback ({message});
          }
        else {
          if (config.flEnableNewUsers) {
            const now = new Date (), emailSecret = utils.getRandomPassword (10);
            userDatabase.theUsers [emailAddress] = {
              emailAddress,
              emailSecret,
              whenCreated: now,
              whenUpdated: now
              ctPageReads: 1,
              whenPageRead: now
              };
            userDatabaseChanged ();
            callback (undefined, emailSecret);
            }
          else {
            const message = "Can't create the user \"" + screenname + "\" because new users are not being accepted here at this time.";
            callback ({message});
            }
          }
        }
      else {
        if (flInDatabase) {
          callback (undefined, theUser.emailSecret);
          }
        else {
          const message = "The user \"" + emailAddress + "\" doesn't have an account on this server.";
          callback ({message});
          }
        }
      });
    }
function statsChanged () {
  daveappserver.saveStats (stats);
  }
function getPrefs (screenname, callback) {
  Changes
    1/28/18; 1:58:14 PM by DW
      This is the initial call of the SYO client to the server. We return the information it needs to startup in one package.
  var myPrefs = {
    screenname: screenname
    };
  var folder = config.usersFolder + screenname + "/";
  var prefsFile = folder + config.fnamePrefs;
  console.log ("getPrefs: screenname == " + screenname + ", prefsFile == " + prefsFile);
  utils.sureFilePath (prefsFile, function () {
    fs.readFile (prefsFile, function (err, data) {
      if (err) {
        myPrefs.prefs = new Object ();
        callback (undefined, myPrefs); //return an empty prefs struct
        }
      else {
        try {
          myPrefs.prefs = JSON.parse (data.toString ());
          callback (undefined, myPrefs);
          }
        catch (err) {
          console.log ("getPrefs: err.message == " + err.message);
          callback (err);
          }
        }
      });
    });
  }
function savePrefs (screenname, jsontext, callback) {
  var prefsFile = config.usersFolder + screenname + "/" + config.fnamePrefs;
  console.log ("savePrefs: screenname == " + screenname + ", jsontext == " + jsontext);
  utils.sureFilePath (prefsFile, function () {
    fs.writeFile (prefsFile, jsontext, function (err) {
      callback (err, true);
      });
    });
  }
function handleHttpRequest (theRequest) {
  var params = theRequest.params, now = new Date ();
  var token = (params.oauth_token !== undefined) ? params.oauth_token : undefined;
  var secret = (params.oauth_token_secret !== undefined) ? params.oauth_token_secret : undefined;
  function returnPlainText (s) {
    theRequest.httpReturn (200, "text/plain", s.toString ());
    }
  function returnHtml (htmltext) {
    theRequest.httpReturn (200, "text/html", htmltext.toString ());
    }
  function returnError (jstruct) {
    theRequest.httpReturn (500, "application/json", utils.jsonStringify (jstruct));
    }
  function returnData (jstruct) {
    Changes
      12/24/20; 3:01:25 PM by DW
        Externally what we call listName is called username.
    if (jstruct === undefined) {
      jstruct = {};
      }
    else {
      if (jstruct.listName !== undefined) {
        jstruct.username = jstruct.listName;
        delete jstruct.listName;
        }
      }
    theRequest.httpReturn (200, "application/json", utils.jsonStringify (jstruct));
    }
  function httpReturn (err, jstruct) {
    if (err) {
      returnError (err);
      }
    else {
      returnData (jstruct);
      }
    }
  switch (theRequest.lowerpath) {
    case "/": //let the app shell handle it, not River6
      return (false);
    case "/now":
      returnPlainText (now);
      return (true);
    case "/stats": case "/stats.json":
      returnData (river6.getStats ());
      return (true);
    default:
      return (false); //not handled
    }
  }
function httpRequest (url, timeout, headers, callback) {
  request (url, function (err, response, data) {
    if (err) {
      callback (err);
      }
    else {
      if (response.statusCode != 200) {
        const message = "The request returned a status code of " + response.statusCode + ".";
        callback ({message});
        }
      else {
        callback (undefined, data)
        }
      }
    });
  }
function publicFileSaved (token, secret, urlOutline) { //12/4/21 by DW
  Changes
    2/12/23; 11:41:57 AM by DW
      I had to turn this off because daytona hasn't been converted to the new identity system.
  if (config.flDaytonaPing) { //2/12/23 by DW
    const url = "http://daytona.scripting.com/ping?url=" + urlOutline + "&oauth_token=" + token + "&oauth_token_secret=" + secret + "&collection=drummeruser";
    httpRequest (url, undefined, undefined, function (err, responsetext) {
      if (err) {
        console.log ("publicFileSaved: err.message == " + err.message);
        }
      else {
        console.log ("publicFileSaved: url == " + url + ", responsetext == " + responsetext);
        }
      })
    }
  }
function everyMinute () {
  }
function everySecond () {
  if (flUserDatabaseChanged) {
    flUserDatabaseChanged = false;
    fs.writeFile (config.fnameUserDatabase, utils.jsonStringify (userDatabase), function (err) {
      if (err) {
        console.log ("everySecond: err.message = " + err.message);
        }
      });
    }
  }
getUserDatabase (function (err) {
  if (err) {
    console.log ("getUserDatabase: err.message = " + err.message);
    }
  var appOptions = {
    productName: myProductName,
    productNameForDisplay: "My Outliner",
    version: myVersion,
    urlServerHomePageSource: "http://scripting.com/code/outlinerserver/home/index.html",
    flWebsocketEnabled: false,
    everySecond,
    everyMinute,
    handleHttpRequest,
    publicFileSaved, //12/4/21 by DW
    addMacroToPagetable: function (pagetable) {
      pagetable.slogan = utils.getRandomSnarkySlogan ();
      },
    addEmailToUserInDatabase, //2/5/23 by DW
    isUserInDatabase //2/6/23 by DW
    }
  daveappserver.start (appOptions, function (appConfig) {
    for (var x in appConfig) {
      config [x] = appConfig [x];
      }
    });
  });