Advertisement
Guest User

Untitled

a guest
Aug 30th, 2018
117
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // There are a lot of uses of `default` in switch statements here.
  2. // This makes that ok.
  3.  
  4. // =================
  5. // L I B R A R I E S
  6. // =================
  7. const ABC = require("@akitabox/constants");
  8. const _ = require("underscore");
  9. const mongoose = require("mongoose");
  10.  
  11. // =================
  12. //   H E L P E R S
  13. // =================
  14. const displayDates = include("src/plugins/mongoose/displayDates");
  15. const toJSONAsync = include("src/plugins/mongoose/toJSONAsync");
  16.  
  17. /**
  18.  * Model representing a user action.
  19.  *
  20.  * @constructs UserActionModel
  21.  *
  22.  * @property {String}           route_name         Route name - the name of the action the user took
  23.  * @property {Date}             time               Timestamp of the action
  24.  * @property {Number}           status             Status code returned for this request. Should be 200 unless
  25.  *                                                 there was an error or a redirect.
  26.  * @property {String}           url_pathname       URL pathname
  27.  * @property {String}           method             Method: GET, POST, PUT, DELETE, PATCH, OPTIONS, TRACE, CONNECT
  28.  * @property {Boolean}          is_api_call        True if action is API call
  29.  * @property {String}           ip                 IP address
  30.  * @property {Application}      calling_app        App that made this request
  31.  * @property {String}           email_domain       Domain of the user's email address saved separately for easy
  32.  *                                                 querying
  33.  * @property {Account}          user               _id of the user taking the action's account
  34.  * @property {Object}           body               Request body
  35.  * @property {Object}           query              Query
  36.  * @property {String}           browser            Browser used
  37.  * @property {String}           operating_system   Operating System
  38.  * @property {String}           user_agent         User agent
  39.  * @property {String}           action             Action
  40.  * @property {Number}           page               Page
  41.  * @property {String}           tab                Tab
  42.  * @property {Account}          account            Building owner, NOT the user
  43.  * @property {Application}      application        Application
  44.  * @property {Attachment}       attachment         Attachment
  45.  * @property {Comment}          comment            Comment
  46.  * @property {Commit}           commit             Commit
  47.  * @property {Document}         document           Document
  48.  * @property {FutureTask}       future_task        Future task
  49.  * @property {Building}         building           Building _id
  50.  * @property {Level}            level              Level
  51.  * @property {ObjectId}         portal             Portal - references a building, but cannot be populated
  52.  * @property {QRCode}           qrcode             QR Code
  53.  * @property {Request}          request            Request
  54.  * @property {Commit}           revision           Commit ID containing the revision for the document
  55.  * @property {Role}             role               Role
  56.  * @property {Tag}              tag                Tag
  57.  * @property {TagCategory}      tag_category       Tag category
  58.  * @property {TagCategoryValue} tag_category_value Tag category value
  59.  * @property {Task}             task               Task
  60.  * @property {PinType}          pin_type           Pin type
  61.  * @property {PinTypeColor}     pin_type_color     Pin type color
  62.  * @property {PinField}         pin_field          Pin field
  63.  * @property {Asset}            asset              Asset
  64.  * @property {Room}             room               Room
  65.  * @property {PinValue}         pin_value          Pin value
  66.  * @property {FMRedirect}       fm_redirect        FM redirect
  67.  */
  68. let UserAction = new mongoose.Schema({
  69.   // Required Fields
  70.   route_name: { type: String, required: true, lowercase: true },
  71.   time: { type: Date, required: true, default: Date.now },
  72.   duration_ms: { type: Number, required: true, Min: 0 },
  73.   status: { type: Number, required: true, default: 200 },
  74.   url_pathname: { type: String, required: true },
  75.   method: {
  76.     type: String,
  77.     required: true,
  78.     uppercase: true,
  79.     enum: [
  80.       "GET",
  81.       "POST",
  82.       "PUT",
  83.       "DELETE",
  84.       "PATCH",
  85.       "OPTIONS",
  86.       "TRACE",
  87.       "CONNECT",
  88.     ],
  89.   },
  90.   is_api_call: { type: Boolean, required: true },
  91.   ip: { type: String, required: true },
  92.  
  93.   // Optional Non-URL Fields
  94.   calling_app: { type: String, ref: "Application", default: null },
  95.   email_domain: { type: String, default: null },
  96.   user: { type: String, ref: "Account", default: null },
  97.   body: { type: Object, default: {} },
  98.   query: { type: Object, default: {} },
  99.   browser: { type: String, default: null },
  100.   operating_system: { type: String, default: null },
  101.   user_agent: { type: String, default: null },
  102.  
  103.   // String URL Parameters
  104.   action: String,
  105.   page: Number,
  106.   tab: String,
  107.  
  108.   // Mongo ID URL Parameters
  109.   account: { type: String, ref: "Account" },
  110.   attachment: { type: mongoose.Schema.Types.ObjectId, ref: "Attachment" },
  111.   application: { type: String, ref: "Application" },
  112.   comment: { type: mongoose.Schema.Types.ObjectId, ref: "Comment" },
  113.   commit: { type: mongoose.Schema.Types.ObjectId, ref: "Commit" },
  114.   document: { type: mongoose.Schema.Types.ObjectId, ref: "Document" },
  115.   future_task: { type: mongoose.Schema.Types.ObjectId, ref: "FutureTask" },
  116.   issue_type: { type: mongoose.Schema.Types.ObjectId, ref: "IssueType" },
  117.   building: { type: mongoose.Schema.Types.ObjectId, ref: "Building" },
  118.   level: { type: mongoose.Schema.Types.ObjectId, ref: "Level" },
  119.   maintenance_type: {
  120.     type: mongoose.Schema.Types.ObjectId,
  121.     ref: "MaintenanceType",
  122.   },
  123.   organization: { type: mongoose.Schema.Types.ObjectId, ref: "Organization" },
  124.   portal: { type: mongoose.Schema.Types.ObjectId },
  125.   qrcode: { type: mongoose.Schema.Types.ObjectId, ref: "QRCode" },
  126.   request: { type: mongoose.Schema.Types.ObjectId, ref: "Request" },
  127.   revision: { type: mongoose.Schema.Types.ObjectId, ref: "Commit" },
  128.   role: { type: mongoose.Schema.Types.ObjectId, ref: "Role" },
  129.   tag: { type: mongoose.Schema.Types.ObjectId, ref: "Tag" },
  130.   tag_category: { type: mongoose.Schema.Types.ObjectId, ref: "TagCategory" },
  131.   tag_category_value: {
  132.     type: mongoose.Schema.Types.ObjectId,
  133.     ref: "TagCategoryValue",
  134.   },
  135.   task: { type: mongoose.Schema.Types.ObjectId, ref: "Task" },
  136.   trade: { type: mongoose.Schema.Types.ObjectId, ref: "Trade" },
  137.   pin_type: { type: mongoose.Schema.Types.ObjectId, ref: "PinType" },
  138.   pin_type_color: { type: mongoose.Schema.Types.ObjectId, ref: "PinTypeColor" },
  139.   pin_field: { type: mongoose.Schema.Types.ObjectId, ref: "PinField" },
  140.   asset: { type: mongoose.Schema.Types.ObjectId, ref: "Asset" },
  141.   pin: { type: mongoose.Schema.Types.ObjectId, ref: "Pin" }, // FIXME: LEAVE THIS IN OR WE CAN'T MIGRATE ANYTHING!!!!!!!!!
  142.   room: { type: mongoose.Schema.Types.ObjectId, ref: "Room" },
  143.   pin_value: { type: mongoose.Schema.Types.ObjectId, ref: "PinValue" },
  144.   fm_redirect: { type: mongoose.Schema.Types.ObjectId, ref: "FMRedirect" },
  145. });
  146.  
  147. // INDICES =========================================================================================================
  148.  
  149. UserAction.index({ route_name: 1 });
  150. UserAction.index({ time: -1 });
  151. UserAction.index({ status: 1 });
  152. UserAction.index({ method: 1 });
  153. UserAction.index({ user: 1 });
  154. UserAction.index({ account: 1 });
  155. UserAction.index({ building: 1 });
  156.  
  157. // PLUGINS =========================================================================================================
  158.  
  159. UserAction.plugin(toJSONAsync);
  160. UserAction.plugin(displayDates, {
  161.   addDisplayAlt: true,
  162. });
  163.  
  164. // VIRTUALS ========================================================================================================
  165.  
  166. /**
  167.  * Creates a human-readable sentence for the action performed in this user_action to be used in entity-level
  168.  * activity feeds.
  169.  *
  170.  * @function getEntityActionDisplay
  171.  * @memberof UserActionModel.prototype
  172.  *
  173.  * @return {String} Action HTML
  174.  */
  175. UserAction.virtual("entity_action_display").get(
  176.   function getEntityActionDisplay() {
  177.     let importantMethods = ["PATCH", "POST", "PUT"];
  178.     let displayHelpers = this.model("UserAction").route_name()[
  179.       this.route_name.toUpperCase()
  180.     ];
  181.  
  182.     if (
  183.       !_.contains(importantMethods, this.method) ||
  184.       this.status !== 200 ||
  185.       _.isUndefined(displayHelpers)
  186.     ) {
  187.       /*
  188.              * Do not care about GET, POST, DELETE, HEAD, OPTIONS, etc user_actions for entity activity feeds.
  189.              * Do not care about unsuccessful requests for entity activity feeds.
  190.              * If displayHelpers is undefined, missing route name.
  191.              */
  192.       return "";
  193.     }
  194.  
  195.     let type = "entity";
  196.     let isHtml = false;
  197.     if ((this.method === "PATCH" || this.method === "PUT") && displayHelpers.updateActionDisplay) {
  198.       return displayHelpers.updateActionDisplay(type, isHtml, this);
  199.     } else if (this.method === "POST" && displayHelpers.createActionDisplay) {
  200.       return displayHelpers.createActionDisplay(type, isHtml, this);
  201.     }
  202.     // We had a display helper for the route_name but not the method.
  203.     return "";
  204.   }
  205. );
  206.  
  207. /**
  208.  * Creates an html element for the action performed in this user_action to be used in entity-level activity feeds.
  209.  *
  210.  * @function getEntityActionHTML
  211.  * @memberof UserActionModel.prototype
  212.  *
  213.  * @return {String} Action HTML
  214.  */
  215. UserAction.virtual("entity_action_html").get(function getEntityActionHTML() {
  216.   let importantMethods = ["PATCH", "POST", "PUT"];
  217.   let displayHelpers = this.model("UserAction").route_name()[
  218.     this.route_name.toUpperCase()
  219.   ];
  220.  
  221.   if (
  222.     !_.contains(importantMethods, this.method) ||
  223.     this.status !== 200 ||
  224.     _.isUndefined(displayHelpers)
  225.   ) {
  226.     /*
  227.              * Do not care about GET, POST, DELETE, HEAD, OPTIONS, etc user_actions for entity activity feeds.
  228.              * Do not care about unsuccessful requests for entity activity feeds.
  229.              * If displayHelpers is undefined, missing route name.
  230.              */
  231.     return "";
  232.   }
  233.  
  234.   let type = "entity";
  235.   let isHtml = true;
  236.   if ((this.method === "PATCH" || this.method === "PUT") && displayHelpers.updateActionDisplay) {
  237.     return displayHelpers.updateActionDisplay(type, isHtml, this);
  238.   } else if (this.method === "POST" && displayHelpers.createActionDisplay) {
  239.     return displayHelpers.createActionDisplay(type, isHtml, this);
  240.   }
  241.   // We had a display helper for the route_name but not the method.
  242.   return "";
  243. });
  244.  
  245. /**
  246.  * Creates a human-readable sentence for the action performed in this user_action to be used in building-level
  247.  * activity feeds.
  248.  *
  249.  * @function getBuildingActionDisplay
  250.  * @memberof UserActionModel.prototype
  251.  *
  252.  * @return {String} Action HTML
  253.  */
  254. UserAction.virtual("building_action_display").get(
  255.   function getBuildingActionDisplay() {
  256.     let importantMethods = ["POST", "PUT", "PATCH", "DELETE"];
  257.     let displayHelpers = this.model("UserAction").route_name()[
  258.       this.route_name.toUpperCase()
  259.     ];
  260.  
  261.     if (
  262.       !_.contains(importantMethods, this.method) ||
  263.       this.status !== 200 ||
  264.       _.isUndefined(displayHelpers)
  265.     ) {
  266.       /*
  267.              * Do not care about GET, HEAD, OPTIONS, etc user_actions for building activity feeds.
  268.              * Do not care about unsuccessful requests for building activity feeds.
  269.              * If displayHelpers is undefined, missing route name.
  270.              */
  271.       return "";
  272.     }
  273.  
  274.     let type = "building";
  275.     let isHtml = false;
  276.     if (this.method === "DELETE" && displayHelpers.deleteActionDisplay) {
  277.       return displayHelpers.deleteActionDisplay(type, isHtml, this);
  278.     } else if (this.method === "POST" && displayHelpers.createActionDisplay) {
  279.       return displayHelpers.createActionDisplay(type, isHtml, this);
  280.     } else if (
  281.       (this.method === "PUT" || this.method === "PATCH") &&
  282.       displayHelpers.updateActionDisplay
  283.     ) {
  284.       return displayHelpers.updateActionDisplay(type, isHtml, this);
  285.     }
  286.     // We had a display helper for the route_name but not the method.
  287.     return "";
  288.   }
  289. );
  290.  
  291. /**
  292.  * Creates an html element for the action performed in this user_action to be used in building-level activity .
  293.  *
  294.  * @function getBuildingActionHTML
  295.  * @memberof UserActionModel.prototype
  296.  *
  297.  * @return {String} Action HTML
  298.  */
  299. UserAction.virtual("building_action_html").get(
  300.   function getBuildingActionHTML() {
  301.     let importantMethods = ["POST", "PUT", "PATCH", "DELETE"];
  302.     let displayHelpers = this.model("UserAction").route_name()[
  303.       this.route_name.toUpperCase()
  304.     ];
  305.  
  306.     if (!_.contains(importantMethods, this.method) || this.status !== 200) {
  307.       /*
  308.              * Do not care about GET, HEAD, OPTIONS, etc user_actions for building activity feeds.
  309.              * Do not care about unsuccessful requests for building activity feeds.
  310.              */
  311.       return "";
  312.     } else if (_.isUndefined(displayHelpers)) {
  313.       /*
  314.              * Missing route name.
  315.              * This shouldn't happen, but if it does then it's fucked and we should just go away.
  316.              */
  317.       return "";
  318.     }
  319.  
  320.     let type = "building";
  321.     let isHtml = true;
  322.  
  323.     if (this.method === "DELETE" && displayHelpers.deleteActionDisplay) {
  324.       return displayHelpers.deleteActionDisplay(type, isHtml, this);
  325.     } else if (this.method === "POST" && displayHelpers.createActionDisplay) {
  326.       return displayHelpers.createActionDisplay(type, isHtml, this);
  327.     } else if (
  328.       (this.method === "PUT" || this.method === "PATCH") &&
  329.       displayHelpers.updateActionDisplay
  330.     ) {
  331.       return displayHelpers.updateActionDisplay(type, isHtml, this);
  332.     }
  333.     // We had a display helper for the route_name but not the method.
  334.     return "";
  335.   }
  336. );
  337.  
  338. // TODO account_action_display for account activity feeds
  339. // TODO organization_action_display for organization activity feeds
  340.  
  341. /**
  342.  * Returns JSON string of body.
  343.  *
  344.  * @function getStringifiedBody
  345.  * @memberof UserActionModel.prototype
  346.  *
  347.  * @return {String} JSON string of body
  348.  */
  349. UserAction.virtual("stringified_body").get(function getStringifiedBody() {
  350.   return JSON.stringify(this.body);
  351. });
  352.  
  353. /**
  354.  * Returns JSON string of query.
  355.  *
  356.  * @function getStringifiedQuery
  357.  * @memberof UserActionModel.prototype
  358.  *
  359.  * @return {String} JSON string of query
  360.  */
  361. UserAction.virtual("stringified_query").get(function getStringifiedQuery() {
  362.   return JSON.stringify(this.query);
  363. });
  364.  
  365. /**
  366.  * Returns true if status is 200, false otherwise.
  367.  *
  368.  * @function isStatusOK
  369.  * @memberof UserActionModel.prototype
  370.  *
  371.  * @return {Boolean} True when status is 200
  372.  */
  373. UserAction.virtual("is_200").get(function isStatusOK() {
  374.   return this.status === 200;
  375. });
  376.  
  377. /**
  378.  * Returns true if status is not 200, false if it is.
  379.  *
  380.  * @function isStatusNotOK
  381.  * @memberof UserActionModel.prototype
  382.  *
  383.  * @return {Boolean} True when status is not 200
  384.  */
  385. UserAction.virtual("is_not_200").get(function isStatusNotOK() {
  386.   return this.status !== 200;
  387. });
  388.  
  389. /**
  390.  * Returns true if status is 500, false otherwise.
  391.  *
  392.  * @function isInternalServerError
  393.  * @memberof UserActionModel.prototype
  394.  *
  395.  * @return {Boolean} True when status is 500
  396.  */
  397. UserAction.virtual("is_500").get(function isInternalServerError() {
  398.   return this.status === 500;
  399. });
  400.  
  401. /**
  402.  * Returns true if user exists, false otherwise.
  403.  *
  404.  * @function hasUser
  405.  * @memberof UserActionModel.prototype
  406.  *
  407.  * @return {Boolean} True when has user
  408.  */
  409. UserAction.virtual("has_user").get(function hasUser() {
  410.   return Boolean(this.user);
  411. });
  412.  
  413. // VALIDATORS ======================================================================================================
  414. require("./UserAction.validators").load(UserAction);
  415.  
  416. // STATIC METHODS ==================================================================================================
  417. /**
  418.  * Gets constants associated with this model
  419.  *
  420.  * @returns {Object}
  421.  */
  422. UserAction.static("constants", function() {
  423.   return ABC.MODELS.USER_ACTION;
  424. });
  425.  
  426. /**
  427.  * Returns fields required to be provided by the user on creation. Called at the router level on a CREATE action.
  428.  * If a field is not provided by the user, a 400 is returned. Other fields are required for creation but are set
  429.  * by the application. If these fields are not set, a 500 should be returned.
  430.  *
  431.  * @function requiredCreateFields
  432.  * @memberof UserActionModel
  433.  * @static
  434.  *
  435.  * @return {Array} List of required create fields.
  436.  */
  437. UserAction.static("requiredCreateFields", function() {
  438.   return ["route_name", "url_pathname", "method", "is_api_call", "ip"];
  439. });
  440.  
  441. /**
  442.  * Returns fields that cannot be set (by the user or application) during creation. These fields are removed from
  443.  * the data provided to the Model just prior to creation at the controller level.
  444.  *
  445.  * @function badCreateFields
  446.  * @memberof UserActionModel
  447.  * @static
  448.  *
  449.  * @return {Array} List of bad create fields
  450.  */
  451. UserAction.static("badCreateFields", function() {
  452.   return ["_id", "__v", "time"];
  453. });
  454.  
  455. // badUpdateFields N/A user does not make API calls to update User Actions
  456.  
  457. /**
  458.  * Returns fields to populate during a query.
  459.  *
  460.  * @function populateFields
  461.  * @memberof UserActionModel
  462.  * @static
  463.  *
  464.  * @return {String} Fields to populate
  465.  */
  466. UserAction.static("populateFields", function() {
  467.   return (
  468.     "asset application comment commit document future_task level pin_field pin_type + " +
  469.     "qrcode request role room tag_category task user"
  470.   );
  471. });
  472.  
  473. /**
  474.  * Returns object of routes and their helper functions.
  475.  *
  476.  * @function route_name
  477.  * @memberof UserActionModel
  478.  * @static
  479.  *
  480.  * @return {Object} Route helpers
  481.  */
  482. UserAction.static("route_name", function() {
  483.   const Asset = mongoose.model("Asset");
  484.   const Application = mongoose.model("Application");
  485.   const Attachment = mongoose.model("Attachment");
  486.   const Comment = mongoose.model("Comment");
  487.   const Document = mongoose.model("Document");
  488.   const FutureTask = mongoose.model("FutureTask");
  489.   const IssueType = mongoose.model("IssueType");
  490.   const Level = mongoose.model("Level");
  491.   const MaintenanceType = mongoose.model("MaintenanceType");
  492.   const Organization = mongoose.model("Organization");
  493.   const PinField = mongoose.model("PinField");
  494.   const PinType = mongoose.model("PinType");
  495.   const PinTypeColor = mongoose.model("PinTypeColor");
  496.   const Building = mongoose.model("Building");
  497.   const Request = mongoose.model("Request");
  498.   const Role = mongoose.model("Role");
  499.   const Room = mongoose.model("Room");
  500.   const Tag = mongoose.model("Tag");
  501.   const TagCategory = mongoose.model("TagCategory");
  502.   const TagCategoryValue = mongoose.model("TagCategoryValue");
  503.   const Task = mongoose.model("Task");
  504.   const Trade = mongoose.model("Trade");
  505.  
  506.   // The list of accepted action names
  507.   let routeHelpers = {
  508.     ACCOUNT_DETAIL: {
  509.       updateActionDisplay: function() {
  510.         // Do not have an account-level activity feed... yet.
  511.         return "";
  512.       },
  513.  
  514.       // Currently do not support deleting accounts.
  515.     },
  516.     ACCOUNT_LIST: {
  517.       // Accounts are created through the registration route.
  518.     },
  519.     ACCOUNT_SETTINGS: {
  520.       // Route only renders account settings (account updates are made to account_detail).
  521.     },
  522.     APPLICATION_DETAIL: {
  523.       updateActionDisplay: function(type, isHtml, userAction) {
  524.         // Don't really care about the special update cases for applications at the moment.
  525.         return standardUpdateActionDisplay(
  526.           type,
  527.           isHtml,
  528.           userAction,
  529.           userAction.application,
  530.           Application
  531.         );
  532.       },
  533.       deleteActionDisplay: function(type, isHtml, userAction) {
  534.         return standardDeleteActionDisplay(
  535.           type,
  536.           isHtml,
  537.           userAction,
  538.           Application
  539.         );
  540.       },
  541.     },
  542.     APPLICATION_LIST: {
  543.       createActionDisplay: function(type, isHtml, userAction) {
  544.         return standardCreateActionDisplay(
  545.           type,
  546.           isHtml,
  547.           userAction,
  548.           Application
  549.         );
  550.       },
  551.     },
  552.     ASSET_DETAIL: {
  553.       updateActionDisplay: function(type, isHtml, userAction) {
  554.         return routeHelpers.PIN_DETAIL.updateActionDisplay(
  555.           type,
  556.           isHtml,
  557.           userAction,
  558.           Asset
  559.         );
  560.       },
  561.       deleteActionDisplay: function(type, isHtml, userAction) {
  562.         return standardDeleteActionDisplay(type, isHtml, userAction, Asset);
  563.       },
  564.     },
  565.     ASSET_LIST: {
  566.       createActionDisplay: function(type, isHtml, userAction) {
  567.         return standardCreateActionDisplay(type, isHtml, userAction, Asset);
  568.       },
  569.     },
  570.     ASSET_VALUE_DETAIL: {
  571.       updateActionDisplay: function(type, isHtml, userAction) {
  572.         return routeHelpers.PIN_VALUE_DETAIL.updateActionDisplay(
  573.           type,
  574.           isHtml,
  575.           userAction,
  576.           Asset
  577.         );
  578.       },
  579.       /*
  580.              * Values are not deleted through this route (deleted when parent pin field is removed from the pin
  581.              * type).
  582.              */
  583.     },
  584.     ASSET_VALUE_LIST: {
  585.       /*
  586.              * Values are not created through this route (created when the parent pin field is created in the pin
  587.              * type).
  588.              */
  589.     },
  590.     ATTACHMENT_DETAIL: {},
  591.     ATTACHMENT_LIST: {
  592.       createActionDisplay: function(type, isHtml, userAction) {
  593.         let user = userAction ? userAction.user : null;
  594.         let username = user ? user.email : "Unknown User";
  595.         let document = userAction.document;
  596.         let filename = document ? document.path : `Unknown file`;
  597.         let url = document ? `${ABC.getDesktop().URL_BASE}${document.media_uri}` : ``;
  598.         let options = {
  599.           target: "_blank",
  600.         };
  601.  
  602.         if (isHtml) {
  603.           return `${addBoldHtmlTag(isHtml, username)} added 1 ${addBoldHtmlTag(isHtml, "photo")}<br>${toHTMLAnchor(filename, url, options)}`;
  604.         } else {
  605.           return `${username} added 1 photo \n ${filename}`;
  606.         }
  607.       },
  608.     },
  609.     BUILDING_DETAIL: {
  610.       updateActionDisplay: function(type, isHtml, userAction) {
  611.         let Model = Building;
  612.         const modelConstants = Model.constants();
  613.         let entityAlias = modelConstants.SINGULAR;
  614.         let entity = userAction.building;
  615.         let actionHtml =
  616.           addBoldHtmlTag(isHtml, userAction.user.email) + " updated ";
  617.         let patchString = null;
  618.  
  619.         if (userAction.method === "PATCH") {
  620.           // PATCH, so we can guess what was changed.
  621.           let getAlias = ABC.MODELS.BUILDING.getAlias;
  622.           let schema = _.keys(Model.schema.paths);
  623.           let body = _.keys(userAction.body);
  624.           let keys = _.intersection(body, schema);
  625.  
  626.           if (_.intersection(keys, Model.addressFields()).length > 0) {
  627.             patchString = addBoldHtmlTag(isHtml, "Address");
  628.           } else {
  629.             patchString = getUpdateFieldString(isHtml, keys, getAlias);
  630.           }
  631.         }
  632.  
  633.         switch (type) {
  634.           // The building is the entity.
  635.           case "entity":
  636.           case "building":
  637.             if (patchString) {
  638.               actionHtml +=
  639.                 "this " +
  640.                 addBoldHtmlTag(isHtml, entityAlias) +
  641.                 "'s " +
  642.                 patchString;
  643.             } else {
  644.               actionHtml +=
  645.                 "this " + addBoldHtmlTag(isHtml, entityAlias) + "'s settings";
  646.             }
  647.  
  648.             break;
  649.  
  650.           // case 'account':
  651.           // case 'organization':
  652.           default:
  653.             // Just send off the standard update message.
  654.             return standardUpdateActionDisplay(
  655.               type,
  656.               isHtml,
  657.               userAction,
  658.               entity,
  659.               Model
  660.             );
  661.         }
  662.  
  663.         return actionHtml;
  664.       },
  665.       deleteActionDisplay: function(type, isHtml, userAction) {
  666.         let Model = Building;
  667.         const modelConstants = Model.constants();
  668.         let entityAlias = modelConstants.SINGULAR;
  669.         let entity = userAction.building;
  670.         let actionHtml;
  671.  
  672.         // Archived a building (unarchive is below in BUILDING_SETTINGS).
  673.         switch (type) {
  674.           // The building is the entity.
  675.           case "entity":
  676.           case "building":
  677.             actionHtml =
  678.               addBoldHtmlTag(isHtml, userAction.user.email) +
  679.               " archived this " +
  680.               addBoldHtmlTag(isHtml, entityAlias);
  681.  
  682.             break;
  683.  
  684.           // case 'account':
  685.           // case 'organization':
  686.           default:
  687.             // Just send off the standard update message.
  688.             return standardUpdateActionDisplay(
  689.               type,
  690.               isHtml,
  691.               userAction,
  692.               entity,
  693.               Model
  694.             );
  695.         }
  696.  
  697.         return actionHtml;
  698.       },
  699.     },
  700.     BUILDING_LIST: {
  701.       createActionDisplay: function() {
  702.         // Do not have an account- or organization-level activity feed... yet.
  703.         return "";
  704.       },
  705.     },
  706.     BUILDING_SETTINGS: {
  707.       /*
  708.              * Route only renders building settings (building updates are made to building_detail and other routes).
  709.              * ... except for restoring an archived building.
  710.              */
  711.       createActionDisplay: function(type, isHtml, userAction) {
  712.         // Building unarchive may be a post and land here in 'create' instead of 'update' :(
  713.         return routeHelpers.BUILDING_SETTINGS.updateActionDisplay(
  714.           type,
  715.           isHtml,
  716.           userAction
  717.         );
  718.       },
  719.       updateActionDisplay: function(type, isHtml, userAction) {
  720.         let Model = Building;
  721.         const modelConstants = Model.constants();
  722.         let entityAlias = modelConstants.SINGULAR;
  723.         let entity = userAction.building;
  724.         let actionHtml;
  725.  
  726.         switch (type) {
  727.           // The building is the entity.
  728.           case "entity":
  729.           case "building":
  730.             actionHtml =
  731.               addBoldHtmlTag(isHtml, userAction.user.email) +
  732.               " restored this " +
  733.               addBoldHtmlTag(isHtml, entityAlias);
  734.             break;
  735.  
  736.           // case 'account':
  737.           // case 'organization':
  738.           default:
  739.             // Just send off the standard update message.
  740.             return standardUpdateActionDisplay(
  741.               type,
  742.               isHtml,
  743.               userAction,
  744.               entity,
  745.               Model
  746.             );
  747.         }
  748.  
  749.         return actionHtml;
  750.       },
  751.     },
  752.     // Comments are queried for separately in the activity feeds.
  753.     COMMENT_DETAIL: {
  754.       updateActionDisplay: function(type, isHtml, userAction) {
  755.         /*
  756.                  * entity: user updated a Note on this Item.
  757.                  * building: user updated a Note on a Item.
  758.                  */
  759.         let Model = Comment;
  760.         const modelConstants = Model.constants();
  761.         let entityAlias = modelConstants.SINGULAR;
  762.         let entity = userAction.comment;
  763.  
  764.         let actionHtml =
  765.           addBoldHtmlTag(isHtml, userAction.user.email) +
  766.           " updated a " +
  767.           addBoldHtmlTag(isHtml, entityAlias) +
  768.           " on";
  769.  
  770.         if (!entity) {
  771.           /*
  772.                      * Looks like population failed because the entity was deleted.
  773.                      * So just use the standard helper.
  774.                      */
  775.           return standardUpdateActionDisplay(
  776.             type,
  777.             isHtml,
  778.             userAction,
  779.             entity,
  780.             Model
  781.           );
  782.         }
  783.  
  784.         switch (type) {
  785.           case "entity":
  786.             actionHtml += " this ";
  787.             break;
  788.           case "building":
  789.             if (entity.entity_alias.toLowerCase() === "building") {
  790.               actionHtml += " this ";
  791.             } else {
  792.               actionHtml += " a ";
  793.             }
  794.             break;
  795.           // case 'account':
  796.           // case 'organization':
  797.           default:
  798.             // Just send off the standard update message.
  799.             return standardUpdateActionDisplay(
  800.               type,
  801.               isHtml,
  802.               userAction,
  803.               entity,
  804.               Model
  805.             );
  806.         }
  807.  
  808.         // Fake some things so we can (try to) make a link.
  809.         if (
  810.           entity.entity &&
  811.           entity[entity.entity] &&
  812.           entity.entity_alias.toLowerCase() !== "building"
  813.         ) {
  814.           let _commentEntity = {
  815.             _id: entity[entity.entity],
  816.           };
  817.  
  818.           actionHtml += addAnchorTag(
  819.             isHtml,
  820.             entity.entity_alias,
  821.             _commentEntity
  822.           );
  823.         } else {
  824.           // For whatever reason, we cant make the link, so just make it bold.
  825.           actionHtml += addBoldHtmlTag(isHtml, entity.entity_alias);
  826.         }
  827.  
  828.         return actionHtml;
  829.       },
  830.       deleteActionDisplay: function(type, isHtml, userAction) {
  831.         /*
  832.                  * Entity was deleted.
  833.                  * So just use the standard helper.
  834.                  */
  835.         return standardDeleteActionDisplay(type, isHtml, userAction, Comment);
  836.       },
  837.     },
  838.     COMMENT_LIST: {
  839.       createActionDisplay: function(type, isHtml, userAction) {
  840.         /*
  841.                  * entity: user added a Note to this Item.
  842.                  * building: user added a Note to a Item.
  843.                  */
  844.         let Model = Comment;
  845.         const modelConstants = Model.constants();
  846.         let entityAlias = modelConstants.SINGULAR;
  847.         let actionHtml =
  848.           addBoldHtmlTag(isHtml, userAction.user.email) +
  849.           " added a " +
  850.           addBoldHtmlTag(isHtml, entityAlias) +
  851.           " to";
  852.  
  853.         // Fake create the item (without saving of course).
  854.         let _entity = new Model(userAction.body);
  855.  
  856.         if (!_entity || !_entity.entity_alias) {
  857.           /*
  858.                      * Looks like population failed because the entity was deleted.
  859.                      * So just use the standard helper.
  860.                      */
  861.           return standardCreateActionDisplay(type, isHtml, userAction, Model);
  862.         }
  863.  
  864.         switch (type) {
  865.           case "entity":
  866.             actionHtml += " this ";
  867.             break;
  868.           case "building":
  869.             if (_entity.entity_alias.toLowerCase() === "building") {
  870.               actionHtml += " this ";
  871.             } else {
  872.               actionHtml += " a ";
  873.             }
  874.             break;
  875.           // case 'account':
  876.           // case 'organization':
  877.           default:
  878.             // Just send off the standard update message.
  879.             return standardCreateActionDisplay(type, isHtml, userAction, Model);
  880.         }
  881.  
  882.         // Fake some things so we can (try to) make a link.
  883.         if (
  884.           _entity.entity &&
  885.           _entity[_entity.entity] &&
  886.           _entity.entity_alias.toLowerCase() !== "building"
  887.         ) {
  888.           let _commentEntity = {
  889.             _id: _entity[_entity.entity],
  890.           };
  891.  
  892.           actionHtml += addAnchorTag(
  893.             isHtml,
  894.             _entity.entity_alias,
  895.             _commentEntity
  896.           );
  897.         } else {
  898.           // For whatever reason, we cant make the link, so just make it bold.
  899.           actionHtml += addBoldHtmlTag(isHtml, _entity.entity_alias);
  900.         }
  901.  
  902.         return actionHtml;
  903.       },
  904.     },
  905.     CONTACT_US: {},
  906.     DEVELOPER_DOCUMENTATION: {},
  907.     DOCUMENT_DETAIL: {
  908.       updateActionDisplay: function(type, isHtml, userAction) {
  909.         let Model = Document;
  910.         const modelConstants = Model.constants();
  911.         let entityAlias = modelConstants.SINGULAR;
  912.         let nameField = modelConstants.NAME_FIELD;
  913.         let entity = userAction.document;
  914.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  915.         let patchString = null;
  916.         let name = null;
  917.  
  918.         if (entity.job || entity.step) {
  919.           return null;
  920.         }
  921.  
  922.         if (entity && entity[nameField]) {
  923.           name = entity[nameField];
  924.         } else if (!entity) {
  925.           /*
  926.                      * Looks like population failed because the entity was deleted.
  927.                      * Just use the standard helper.
  928.                      */
  929.           return standardUpdateActionDisplay(
  930.             type,
  931.             isHtml,
  932.             userAction,
  933.             entity,
  934.             Model
  935.           );
  936.         }
  937.  
  938.         switch (userAction.body.action) {
  939.           case "rename":
  940.             actionHtml += " renamed ";
  941.             break;
  942.           case "archive":
  943.             actionHtml += " archived ";
  944.             break;
  945.           case "unarchive":
  946.             actionHtml += " restored ";
  947.             break;
  948.           case "revert":
  949.             actionHtml += " reverted ";
  950.             break;
  951.           case "rotate":
  952.             actionHtml += " rotated ";
  953.             break;
  954.           default:
  955.             // Just send off the standard update message.
  956.             return standardUpdateActionDisplay(
  957.               type,
  958.               isHtml,
  959.               userAction,
  960.               entity,
  961.               Model
  962.             );
  963.         }
  964.  
  965.         switch (type) {
  966.           case "entity":
  967.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  968.  
  969.             if (patchString) {
  970.               actionHtml += "'s " + patchString;
  971.             }
  972.  
  973.             break;
  974.  
  975.           case "building":
  976.             if (patchString) {
  977.               actionHtml +=
  978.                 "the " +
  979.                 patchString +
  980.                 " of " +
  981.                 addBoldHtmlTag(isHtml, entityAlias) +
  982.                 " - " +
  983.                 addAnchorTag(isHtml, name, entity);
  984.             } else {
  985.               actionHtml +=
  986.                 addBoldHtmlTag(isHtml, entityAlias) +
  987.                 " - " +
  988.                 addAnchorTag(isHtml, name, entity);
  989.             }
  990.             break;
  991.           // case 'account':
  992.           // case 'organization':
  993.           default:
  994.             // Just send off the standard update message.
  995.             return standardUpdateActionDisplay(
  996.               type,
  997.               isHtml,
  998.               userAction,
  999.               entity,
  1000.               Model
  1001.             );
  1002.         }
  1003.  
  1004.         return actionHtml;
  1005.       },
  1006.       deleteActionDisplay: function(type, isHtml, userAction) {
  1007.         return standardDeleteActionDisplay(type, isHtml, userAction, Document);
  1008.       },
  1009.     },
  1010.     DOCUMENT_DOWNLOAD: {},
  1011.     DOCUMENT_LIST: {
  1012.       createActionDisplay: function(type, isHtml, userAction) {
  1013.         return routeHelpers.UPLOAD.createActionDisplay(
  1014.           type,
  1015.           isHtml,
  1016.           userAction
  1017.         );
  1018.       },
  1019.     },
  1020.  
  1021.     // Tracks document views in the fac.
  1022.     DOCUMENT_VIEW: {},
  1023.     FM_REDIRECT_DETAIL: {},
  1024.     FM_REDIRECT_LIST: {},
  1025.     FORGOT_PASSWORD: {
  1026.       createActionDisplay: function() {
  1027.         /*
  1028.                  * POST to forget password.
  1029.                  * Do not have an account-level activity feed... yet.
  1030.                  */
  1031.         return "";
  1032.       },
  1033.     },
  1034.     FUTURE_TASK_DETAIL: {
  1035.       updateActionDisplay: function(type, isHtml, userAction) {
  1036.         let Model = FutureTask;
  1037.         const modelConstants = Model.constants();
  1038.         let entityAlias = modelConstants.SINGULAR;
  1039.         let nameField = modelConstants.NAME_FIELD;
  1040.         let entity = userAction.future_task;
  1041.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1042.         let patchString = null;
  1043.         let name = null;
  1044.  
  1045.         if (entity && entity[nameField]) {
  1046.           name = entity[nameField];
  1047.         } else if (!entity) {
  1048.           /*
  1049.                      * Looks like population failed because the entity was deleted.
  1050.                      * Just use the standard helper.
  1051.                      */
  1052.           return standardUpdateActionDisplay(
  1053.             type,
  1054.             isHtml,
  1055.             userAction,
  1056.             entity,
  1057.             Model
  1058.           );
  1059.         }
  1060.  
  1061.         switch (userAction.body.action) {
  1062.           case "update":
  1063.             actionHtml += " updated ";
  1064.  
  1065.             if (userAction.method === "PATCH") {
  1066.               // PATCH, so we can guess what was changed.
  1067.               let getAlias = ABC.MODELS.MAINTENANCE_SCHEDULE.getAlias;
  1068.               let schema = _.keys(Model.schema.paths);
  1069.               let body = _.keys(userAction.body);
  1070.               let keys = _.intersection(body, schema);
  1071.  
  1072.               if (
  1073.                 _.intersection(keys, Model.repeatingConditionFields()).length >
  1074.                 0
  1075.               ) {
  1076.                 patchString = "Frequency";
  1077.               } else {
  1078.                 patchString = getUpdateFieldString(isHtml, keys, getAlias);
  1079.               }
  1080.             }
  1081.             break;
  1082.  
  1083.           case "cancel":
  1084.             actionHtml += " canceled ";
  1085.             break;
  1086.           case "reopen":
  1087.             actionHtml += " reopened ";
  1088.             break;
  1089.           default:
  1090.             // Just send off the standard update message.
  1091.             return standardUpdateActionDisplay(
  1092.               type,
  1093.               isHtml,
  1094.               userAction,
  1095.               entity,
  1096.               Model
  1097.             );
  1098.         }
  1099.  
  1100.         switch (type) {
  1101.           case "entity":
  1102.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1103.  
  1104.             if (patchString) {
  1105.               actionHtml += "'s " + patchString;
  1106.             }
  1107.  
  1108.             break;
  1109.  
  1110.           case "building":
  1111.             if (patchString) {
  1112.               actionHtml +=
  1113.                 patchString +
  1114.                 " in " +
  1115.                 addBoldHtmlTag(isHtml, entityAlias) +
  1116.                 " - " +
  1117.                 addAnchorTag(isHtml, name, entity);
  1118.             } else {
  1119.               actionHtml +=
  1120.                 addBoldHtmlTag(isHtml, entityAlias) +
  1121.                 " - " +
  1122.                 addAnchorTag(isHtml, name, entity);
  1123.             }
  1124.  
  1125.             break;
  1126.  
  1127.           // case 'account':
  1128.           // case 'organization':
  1129.           default:
  1130.             // Just send off the standard update message.
  1131.             return standardUpdateActionDisplay(
  1132.               type,
  1133.               isHtml,
  1134.               userAction,
  1135.               entity,
  1136.               Model
  1137.             );
  1138.         }
  1139.  
  1140.         return actionHtml;
  1141.       },
  1142.       deleteActionDisplay: function(type, isHtml, userAction) {
  1143.         return standardDeleteActionDisplay(
  1144.           type,
  1145.           isHtml,
  1146.           userAction,
  1147.           FutureTask
  1148.         );
  1149.       },
  1150.     },
  1151.     FUTURE_TASK_LIST: {
  1152.       createActionDisplay: function(type, isHtml, userAction) {
  1153.         return standardCreateActionDisplay(
  1154.           type,
  1155.           isHtml,
  1156.           userAction,
  1157.           FutureTask
  1158.         );
  1159.       },
  1160.     },
  1161.     HELP: {},
  1162.     HOME: {},
  1163.     IS_AD_DOMAIN: {},
  1164.     ISSUE_TYPE_DETAIL: {
  1165.       updateActionDisplay: function(type, isHtml, userAction) {
  1166.         let Model = IssueType;
  1167.         const modelConstants = Model.constants();
  1168.         let entityAlias = modelConstants.SINGULAR;
  1169.         let nameField = modelConstants.NAME_FIELD;
  1170.         let entity = userAction.issue_type;
  1171.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1172.         let name = null;
  1173.  
  1174.         if (entity && entity[nameField]) {
  1175.           name = entity[nameField];
  1176.         } else if (!entity) {
  1177.           /*
  1178.                      * Looks like population failed because the entity was deleted.
  1179.                      * Just use the standard helper.
  1180.                      */
  1181.           return standardUpdateActionDisplay(
  1182.             type,
  1183.             isHtml,
  1184.             userAction,
  1185.             entity,
  1186.             Model
  1187.           );
  1188.         }
  1189.  
  1190.         switch (userAction.body.action) {
  1191.           case "archive":
  1192.             actionHtml += " archived ";
  1193.             break;
  1194.           case "restore":
  1195.             actionHtml += " restored ";
  1196.             break;
  1197.           case "update":
  1198.             actionHtml += " updated ";
  1199.             break;
  1200.           default:
  1201.             // Just send off the standard update message.
  1202.             return standardUpdateActionDisplay(
  1203.               type,
  1204.               isHtml,
  1205.               userAction,
  1206.               entity,
  1207.               Model
  1208.             );
  1209.         }
  1210.  
  1211.         switch (type) {
  1212.           case "entity":
  1213.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1214.             break;
  1215.           case "building":
  1216.             actionHtml +=
  1217.               addBoldHtmlTag(isHtml, entityAlias) +
  1218.               " - " +
  1219.               addAnchorTag(isHtml, name, entity);
  1220.             break;
  1221.           // case 'account':
  1222.           // case 'organization':
  1223.           default:
  1224.             // Just send off the standard update message.
  1225.             return standardUpdateActionDisplay(
  1226.               type,
  1227.               isHtml,
  1228.               userAction,
  1229.               entity,
  1230.               Model
  1231.             );
  1232.         }
  1233.  
  1234.         return actionHtml;
  1235.       },
  1236.       deleteActionDisplay: function(type, isHtml, userAction) {
  1237.         return standardDeleteActionDisplay(type, isHtml, userAction, IssueType);
  1238.       },
  1239.     },
  1240.     ISSUE_TYPE_LIST: {
  1241.       createActionDisplay: function(type, isHtml, userAction) {
  1242.         return standardCreateActionDisplay(type, isHtml, userAction, IssueType);
  1243.       },
  1244.     },
  1245.     LANDING: {},
  1246.     LEVEL_DETAIL: {
  1247.       updateActionDisplay: function(type, isHtml, userAction) {
  1248.         let Model = Level;
  1249.         const modelConstants = Model.constants();
  1250.         let entityAlias = modelConstants.SINGULAR;
  1251.         let nameField = modelConstants.NAME_FIELD;
  1252.         let entity = userAction.level;
  1253.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1254.         let patchString = null;
  1255.         let name = null;
  1256.  
  1257.         if (entity && entity[nameField]) {
  1258.           name = entity[nameField];
  1259.         } else if (!entity) {
  1260.           /*
  1261.                      * Looks like population failed because the entity was deleted.
  1262.                      * Just use the standard helper.
  1263.                      */
  1264.           return standardUpdateActionDisplay(
  1265.             type,
  1266.             isHtml,
  1267.             userAction,
  1268.             entity,
  1269.             Model
  1270.           );
  1271.         }
  1272.  
  1273.         actionHtml += " updated ";
  1274.  
  1275.         switch (userAction.body.action) {
  1276.           case "updateDocument":
  1277.             patchString = "Floor Plan";
  1278.             break;
  1279.  
  1280.           default: {
  1281.             if (userAction.method !== "PATCH") {
  1282.               // Just send off the standard update message.
  1283.               return standardUpdateActionDisplay(
  1284.                 type,
  1285.                 isHtml,
  1286.                 userAction,
  1287.                 entity,
  1288.                 Model
  1289.               );
  1290.             }
  1291.  
  1292.             // PATCH, so we can guess what was changed.
  1293.             let getAlias = ABC.MODELS.FLOOR.getAlias;
  1294.             let schema = _.keys(Model.schema.paths);
  1295.             let body = _.keys(userAction.body);
  1296.             let keys = _.intersection(body, schema);
  1297.  
  1298.             patchString = getUpdateFieldString(isHtml, keys, getAlias);
  1299.  
  1300.             break;
  1301.           }
  1302.         }
  1303.  
  1304.         switch (type) {
  1305.           case "entity":
  1306.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1307.  
  1308.             if (patchString) {
  1309.               actionHtml += "'s " + patchString;
  1310.             }
  1311.  
  1312.             break;
  1313.           case "building":
  1314.             if (patchString) {
  1315.               actionHtml +=
  1316.                 "the " +
  1317.                 patchString +
  1318.                 " of " +
  1319.                 addBoldHtmlTag(isHtml, entityAlias) +
  1320.                 " - " +
  1321.                 addAnchorTag(isHtml, name, entity);
  1322.             } else {
  1323.               actionHtml +=
  1324.                 addBoldHtmlTag(isHtml, entityAlias) +
  1325.                 " - " +
  1326.                 addAnchorTag(isHtml, name, entity);
  1327.             }
  1328.             break;
  1329.           // case 'account':
  1330.           // case 'organization':
  1331.           default:
  1332.             // Just send off the standard update message.
  1333.             return standardUpdateActionDisplay(
  1334.               type,
  1335.               isHtml,
  1336.               userAction,
  1337.               entity,
  1338.               Model
  1339.             );
  1340.         }
  1341.  
  1342.         return actionHtml;
  1343.       },
  1344.       deleteActionDisplay: function(type, isHtml, userAction) {
  1345.         return standardDeleteActionDisplay(type, isHtml, userAction, Level);
  1346.       },
  1347.     },
  1348.     LEVEL_LIST: {
  1349.       createActionDisplay: function(type, isHtml, userAction) {
  1350.         return standardCreateActionDisplay(type, isHtml, userAction, Level);
  1351.       },
  1352.     },
  1353.     LOGIN: {
  1354.       createActionDisplay: function() {
  1355.         /*
  1356.                  * POST to login.
  1357.                  * Do not have an account-level activity feed... yet.
  1358.                  */
  1359.         return "";
  1360.       },
  1361.     },
  1362.  
  1363.     // GET to logout.
  1364.     LOGOUT: {},
  1365.     LOGOUT_APP: {},
  1366.     MAINTENANCE_TYPE_DETAIL: {
  1367.       updateActionDisplay: function(type, isHtml, userAction) {
  1368.         let Model = MaintenanceType;
  1369.         const modelConstants = Model.constants();
  1370.         let entityAlias = modelConstants.SINGULAR;
  1371.         let nameField = modelConstants.NAME_FIELD;
  1372.         let entity = userAction.maintenance_type;
  1373.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1374.         let name = null;
  1375.  
  1376.         if (entity && entity[nameField]) {
  1377.           name = entity[nameField];
  1378.         } else if (!entity) {
  1379.           /*
  1380.                      * Looks like population failed because the entity was deleted.
  1381.                      * Just use the standard helper.
  1382.                      */
  1383.           return standardUpdateActionDisplay(
  1384.             type,
  1385.             isHtml,
  1386.             userAction,
  1387.             entity,
  1388.             Model
  1389.           );
  1390.         }
  1391.  
  1392.         switch (userAction.body.action) {
  1393.           case "archive":
  1394.             actionHtml += " archived ";
  1395.             break;
  1396.           case "restore":
  1397.             actionHtml += " restored ";
  1398.             break;
  1399.           case "update":
  1400.             actionHtml += " updated ";
  1401.             break;
  1402.           default:
  1403.             // Just send off the standard update message.
  1404.             return standardUpdateActionDisplay(
  1405.               type,
  1406.               isHtml,
  1407.               userAction,
  1408.               entity,
  1409.               Model
  1410.             );
  1411.         }
  1412.  
  1413.         switch (type) {
  1414.           case "entity":
  1415.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1416.             break;
  1417.           case "building":
  1418.             actionHtml +=
  1419.               addBoldHtmlTag(isHtml, entityAlias) +
  1420.               " - " +
  1421.               addAnchorTag(isHtml, name, entity);
  1422.             break;
  1423.           // case 'account':
  1424.           // case 'organization':
  1425.           default:
  1426.             // Just send off the standard update message.
  1427.             return standardUpdateActionDisplay(
  1428.               type,
  1429.               isHtml,
  1430.               userAction,
  1431.               entity,
  1432.               Model
  1433.             );
  1434.         }
  1435.  
  1436.         return actionHtml;
  1437.       },
  1438.       deleteActionDisplay: function(type, isHtml, userAction) {
  1439.         return standardDeleteActionDisplay(
  1440.           type,
  1441.           isHtml,
  1442.           userAction,
  1443.           MaintenanceType
  1444.         );
  1445.       },
  1446.     },
  1447.     MAINTENANCE_TYPE_LIST: {
  1448.       createActionDisplay: function(type, isHtml, userAction) {
  1449.         return standardCreateActionDisplay(
  1450.           type,
  1451.           isHtml,
  1452.           userAction,
  1453.           MaintenanceType
  1454.         );
  1455.       },
  1456.     },
  1457.     ORGANIZATION_LIST: {
  1458.       // no reason to have a createActionDisplay
  1459.     },
  1460.     ORGANIZATION_DETAIL: {
  1461.       updateActionDisplay: function(type, isHtml, userAction) {
  1462.         let Model = Organization;
  1463.         const modelConstants = Model.constants();
  1464.         let entityAlias = modelConstants.SINGULAR;
  1465.         let entity = userAction.organization;
  1466.         let actionHtml =
  1467.           addBoldHtmlTag(isHtml, userAction.user.email) + " updated ";
  1468.         let patchString = null;
  1469.  
  1470.         if (userAction.method === "PATCH") {
  1471.           // PATCH, so we can guess what was changed.
  1472.           let getAlias = ABC.MODELS.ORGANIZATION.getAlias;
  1473.           let schema = _.keys(Model.schema.paths);
  1474.           let body = _.keys(userAction.body);
  1475.           let keys = _.intersection(body, schema);
  1476.           patchString = getUpdateFieldString(isHtml, keys, getAlias);
  1477.         }
  1478.  
  1479.         switch (type) {
  1480.           // The organization is the entity.
  1481.           case "entity":
  1482.           case "organization":
  1483.             if (patchString) {
  1484.               actionHtml +=
  1485.                 "this " +
  1486.                 addBoldHtmlTag(isHtml, entityAlias) +
  1487.                 "'s " +
  1488.                 patchString;
  1489.             } else {
  1490.               actionHtml +=
  1491.                 "this " + addBoldHtmlTag(isHtml, entityAlias) + "'s settings";
  1492.             }
  1493.  
  1494.             break;
  1495.  
  1496.           // case 'account':
  1497.           // case 'organization':
  1498.           default:
  1499.             // Just send off the standard update message.
  1500.             return standardUpdateActionDisplay(
  1501.               type,
  1502.               isHtml,
  1503.               userAction,
  1504.               entity,
  1505.               Model
  1506.             );
  1507.         }
  1508.  
  1509.         return actionHtml;
  1510.       },
  1511.     },
  1512.     OAUTH2_SETUP: {},
  1513.     PIN_DETAIL: {
  1514.       updateActionDisplay: function(type, isHtml, userAction, Model) {
  1515.         if (_.isEmpty(Model)) {
  1516.           if (userAction.asset) Model = Asset;
  1517.           else if (userAction.room) Model = Room;
  1518.         }
  1519.         const modelConstants = Model.constants();
  1520.         let entityAlias = modelConstants.SINGULAR;
  1521.         let nameField = modelConstants.NAME_FIELD;
  1522.         let entity = userAction[modelConstants.MODEL];
  1523.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1524.         let patchString = null;
  1525.         let name = null;
  1526.  
  1527.         if (entity && entity[nameField]) {
  1528.           name = entity[nameField];
  1529.         } else if (!entity) {
  1530.           /*
  1531.                      * Looks like population failed because the entity was deleted.
  1532.                      * Just use the standard helper.
  1533.                      */
  1534.           return standardUpdateActionDisplay(
  1535.             type,
  1536.             isHtml,
  1537.             userAction,
  1538.             entity,
  1539.             Model
  1540.           );
  1541.         }
  1542.  
  1543.         switch (userAction.body.action) {
  1544.           case "update":
  1545.             actionHtml += " updated ";
  1546.             break;
  1547.  
  1548.           case "setLocation":
  1549.             actionHtml += " updated ";
  1550.             patchString = addBoldHtmlTag(isHtml, "Location");
  1551.             break;
  1552.  
  1553.           case "updateRoom":
  1554.             // Can only be an Asset for this action.
  1555.             entityAlias = ABC.MODELS.ASSET.SINGULAR;
  1556.             actionHtml += " updated ";
  1557.             patchString = addBoldHtmlTag(isHtml, ABC.MODELS.ROOM.SINGULAR);
  1558.             break;
  1559.  
  1560.           case "updateLevel":
  1561.             actionHtml += " updated ";
  1562.             patchString = addBoldHtmlTag(isHtml, ABC.MODELS.FLOOR.SINGULAR);
  1563.             break;
  1564.  
  1565.           default:
  1566.             // Just send off the standard update message.
  1567.             return standardUpdateActionDisplay(
  1568.               type,
  1569.               isHtml,
  1570.               userAction,
  1571.               entity,
  1572.               Model
  1573.             );
  1574.         }
  1575.  
  1576.         switch (type) {
  1577.           case "entity":
  1578.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1579.  
  1580.             if (patchString) {
  1581.               actionHtml += "'s " + patchString;
  1582.             }
  1583.  
  1584.             break;
  1585.  
  1586.           case "building":
  1587.             if (patchString) {
  1588.               actionHtml +=
  1589.                 "the " +
  1590.                 patchString +
  1591.                 " in " +
  1592.                 addBoldHtmlTag(isHtml, entityAlias) +
  1593.                 " - " +
  1594.                 addAnchorTag(isHtml, name, entity);
  1595.             } else {
  1596.               actionHtml +=
  1597.                 addBoldHtmlTag(isHtml, entityAlias) +
  1598.                 " - " +
  1599.                 addAnchorTag(isHtml, name, entity);
  1600.             }
  1601.             break;
  1602.  
  1603.           // case 'account':
  1604.           // case 'organization':
  1605.           default:
  1606.             // Just send off the standard update message.
  1607.             return standardUpdateActionDisplay(
  1608.               type,
  1609.               isHtml,
  1610.               userAction,
  1611.               entity,
  1612.               Model
  1613.             );
  1614.         }
  1615.  
  1616.         return actionHtml;
  1617.       },
  1618.     },
  1619.     PIN_FIELD_DETAIL: {
  1620.       updateActionDisplay: function(type, isHtml, userAction) {
  1621.         // TODO get all the special update cases
  1622.         return childUpdateActionDisplay(
  1623.           type,
  1624.           isHtml,
  1625.           userAction,
  1626.           userAction.pin_field,
  1627.           PinField,
  1628.           userAction.pin_type,
  1629.           PinType
  1630.         );
  1631.       },
  1632.       deleteActionDisplay: function(type, isHtml, userAction) {
  1633.         return childDeleteActionDisplay(
  1634.           type,
  1635.           isHtml,
  1636.           userAction,
  1637.           PinField,
  1638.           userAction.pin_type,
  1639.           PinType
  1640.         );
  1641.       },
  1642.     },
  1643.     PIN_FIELD_LIST: {
  1644.       createActionDisplay: function(type, isHtml, userAction) {
  1645.         return childCreateActionDisplay(
  1646.           type,
  1647.           isHtml,
  1648.           userAction,
  1649.           PinField,
  1650.           userAction.pin_type,
  1651.           PinType
  1652.         );
  1653.       },
  1654.     },
  1655.  
  1656.     // Only GET calls for hard-coded trees not stored in the pin field itself.
  1657.     PIN_FIELD_TREE: {},
  1658.     PIN_TYPE_COLOR_DETAIL: {
  1659.       updateActionDisplay: function(type, isHtml, userAction) {
  1660.         // TODO get all the special update cases
  1661.         return childUpdateActionDisplay(
  1662.           type,
  1663.           isHtml,
  1664.           userAction,
  1665.           userAction.pin_type_color,
  1666.           PinTypeColor,
  1667.           userAction.pin_type,
  1668.           PinType
  1669.         );
  1670.       },
  1671.       deleteActionDisplay: function(type, isHtml, userAction) {
  1672.         return childDeleteActionDisplay(
  1673.           type,
  1674.           isHtml,
  1675.           userAction,
  1676.           PinTypeColor,
  1677.           userAction.pin_type,
  1678.           PinType
  1679.         );
  1680.       },
  1681.     },
  1682.     PIN_TYPE_COLOR_LIST: {
  1683.       createActionDisplay: function(type, isHtml, userAction) {
  1684.         return childCreateActionDisplay(
  1685.           type,
  1686.           isHtml,
  1687.           userAction,
  1688.           PinTypeColor,
  1689.           userAction.pin_type,
  1690.           PinType
  1691.         );
  1692.       },
  1693.     },
  1694.     PIN_TYPE_DETAIL: {
  1695.       updateActionDisplay: function(type, isHtml, userAction) {
  1696.         // TODO get all the special update cases
  1697.         return standardUpdateActionDisplay(
  1698.           type,
  1699.           isHtml,
  1700.           userAction,
  1701.           userAction.pin_type,
  1702.           PinType
  1703.         );
  1704.       },
  1705.       deleteActionDisplay: function(type, isHtml, userAction) {
  1706.         return standardDeleteActionDisplay(type, isHtml, userAction, PinType);
  1707.       },
  1708.     },
  1709.     PIN_TYPE_LIST: {
  1710.       createActionDisplay: function(type, isHtml, userAction) {
  1711.         return standardCreateActionDisplay(type, isHtml, userAction, PinType);
  1712.       },
  1713.     },
  1714.     PIN_VALUE_DETAIL: {
  1715.       updateActionDisplay: function(type, isHtml, userAction, Model) {
  1716.         if (_.isEmpty(Model)) {
  1717.           if (userAction.asset) Model = Asset;
  1718.           else if (userAction.room) Model = Room;
  1719.         }
  1720.         const modelConstants = Model.constants();
  1721.         let entityAlias = modelConstants.SINGULAR;
  1722.         let nameField = modelConstants.NAME_FIELD;
  1723.         let entity = userAction[modelConstants.MODEL];
  1724.         let parentEntity = userAction.pin_type;
  1725.         let actionHtml =
  1726.           addBoldHtmlTag(isHtml, userAction.user.email) + " updated ";
  1727.         let patchString = null;
  1728.         let name = null;
  1729.  
  1730.         if (entity && entity[nameField]) {
  1731.           name = entity[nameField];
  1732.         } else if (!entity) {
  1733.           /*
  1734.                      * Looks like population failed because the entity was deleted.
  1735.                      * Just use the standard helper.
  1736.                      */
  1737.           return standardUpdateActionDisplay(
  1738.             type,
  1739.             isHtml,
  1740.             userAction,
  1741.             entity,
  1742.             Model
  1743.           );
  1744.         }
  1745.  
  1746.         // Try to set patchString to the pin field corresponding to the value that was updated.
  1747.         if (
  1748.           entity &&
  1749.           _.isArray(entity.values) &&
  1750.           parentEntity &&
  1751.           _.isArray(parentEntity.fields) &&
  1752.           userAction.pin_value
  1753.         ) {
  1754.           // Find the value so we can get its pin field id.
  1755.           let pinValue = entity.values.id(userAction.pin_value);
  1756.  
  1757.           if (pinValue) {
  1758.             // Find the value's pin field in the pin type.
  1759.             let pinField = parentEntity.fields.id(pinValue.pinField);
  1760.  
  1761.             if (pinField && pinField.name) {
  1762.               // Finally have the pin field name.
  1763.               patchString = addBoldHtmlTag(isHtml, pinField.name);
  1764.             }
  1765.           }
  1766.         }
  1767.  
  1768.         switch (type) {
  1769.           case "entity":
  1770.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1771.  
  1772.             if (patchString) {
  1773.               actionHtml += "'s " + patchString;
  1774.             }
  1775.  
  1776.             break;
  1777.  
  1778.           case "building":
  1779.             if (patchString) {
  1780.               actionHtml +=
  1781.                 "the " +
  1782.                 patchString +
  1783.                 " in " +
  1784.                 addBoldHtmlTag(isHtml, entityAlias) +
  1785.                 " - " +
  1786.                 addAnchorTag(isHtml, name, entity);
  1787.             } else {
  1788.               actionHtml +=
  1789.                 addBoldHtmlTag(isHtml, entityAlias) +
  1790.                 " - " +
  1791.                 addAnchorTag(isHtml, name, entity);
  1792.             }
  1793.             break;
  1794.  
  1795.           // case 'account':
  1796.           // case 'organization':
  1797.           default:
  1798.             // Just send off the standard update message.
  1799.             return standardUpdateActionDisplay(
  1800.               type,
  1801.               isHtml,
  1802.               userAction,
  1803.               entity,
  1804.               Model
  1805.             );
  1806.         }
  1807.  
  1808.         return actionHtml;
  1809.       },
  1810.       /*
  1811.              * Values are not deleted through this route (deleted when parent pin field is removed from the pin
  1812.              * type).
  1813.              */
  1814.     },
  1815.  
  1816.     // Values are not created through this route (created when the parent pin field is created in the pin type).
  1817.     PIN_VALUE_LIST: {},
  1818.     PORTAL_TOKEN: {},
  1819.     PRIVACY_POLICY: {},
  1820.     // Only GET calls.
  1821.     QRCODE_LIST: {},
  1822.     // Only GET calls.
  1823.     QRCODE_REDIRECT: {},
  1824.     REGISTER: {
  1825.       createActionDisplay: function() {
  1826.         /*
  1827.                  * POST to register new accounts.
  1828.                  * Do not have an account-level activity feed... yet.
  1829.                  */
  1830.         return "";
  1831.       },
  1832.     },
  1833.     REQUEST_DETAIL: {
  1834.       updateActionDisplay: function(type, isHtml, userAction) {
  1835.         let Model = Request;
  1836.         const modelConstants = Model.constants();
  1837.         let entityAlias = modelConstants.SINGULAR;
  1838.         let entity = userAction.request;
  1839.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  1840.  
  1841.         if (!entity) {
  1842.           /*
  1843.                      * Looks like population failed because the entity was deleted.
  1844.                      * Use the standard helper.
  1845.                      */
  1846.           return standardUpdateActionDisplay(
  1847.             type,
  1848.             isHtml,
  1849.             userAction,
  1850.             entity,
  1851.             Model
  1852.           );
  1853.         }
  1854.  
  1855.         switch (userAction.body.action) {
  1856.           case "deny":
  1857.             actionHtml += " denied ";
  1858.             break;
  1859.           case "reopen":
  1860.             actionHtml += " reopened ";
  1861.             break;
  1862.           case "update":
  1863.           default:
  1864.             // Send off the standard update message.
  1865.             return standardUpdateActionDisplay(
  1866.               type,
  1867.               isHtml,
  1868.               userAction,
  1869.               entity,
  1870.               Model
  1871.             );
  1872.         }
  1873.  
  1874.         switch (type) {
  1875.           case "entity":
  1876.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  1877.             break;
  1878.  
  1879.           case "building":
  1880.             actionHtml +=
  1881.               "the " +
  1882.               addAnchorTag(isHtml, entityAlias, entity) +
  1883.               " submitted by " +
  1884.               addBoldHtmlTag(isHtml, entity.requester_email) +
  1885.               " on " +
  1886.               entity.cre_date;
  1887.  
  1888.             break;
  1889.  
  1890.           // case 'account':
  1891.           // case 'organization':
  1892.           default:
  1893.             // Send off the standard update message.
  1894.             return standardUpdateActionDisplay(
  1895.               type,
  1896.               isHtml,
  1897.               userAction,
  1898.               entity,
  1899.               Model
  1900.             );
  1901.         }
  1902.  
  1903.         return actionHtml;
  1904.       },
  1905.       deleteActionDisplay: function(type, isHtml, userAction) {
  1906.         return standardDeleteActionDisplay(type, isHtml, userAction, Request);
  1907.       },
  1908.     },
  1909.     REQUEST_LIST: {
  1910.       createActionDisplay: function(type, isHtml, userAction) {
  1911.         /*
  1912.                  * Cannot use standardCreateActionDisplay (Request.nameField() is not
  1913.                  * applicable and want a different verb 'submitted'). And user may not
  1914.                  * be set for requests made from Portal.
  1915.                  */
  1916.         let Model = Request;
  1917.         const modelConstants = Model.constants();
  1918.         let entityAlias = modelConstants.SINGULAR;
  1919.         let user;
  1920.         if (userAction.user && _.isString(userAction.user.email)) {
  1921.           user = addBoldHtmlTag(isHtml, userAction.user.email);
  1922.         } else {
  1923.           // from the portal
  1924.           user = "A building occupant";
  1925.         }
  1926.         return user + " submitted a new " + addBoldHtmlTag(isHtml, entityAlias);
  1927.       },
  1928.     },
  1929.  
  1930.     // Only GET calls for request portals.
  1931.     REQUEST_PORTAL: {},
  1932.     RESET_PASSWORD: {
  1933.       createActionDisplay: function() {
  1934.         /*
  1935.                  * POST to reset passwords.
  1936.                  * Do not have an account-level activity feed... yet.
  1937.                  */
  1938.         return "";
  1939.       },
  1940.     },
  1941.  
  1942.     // Only GET calls.
  1943.     REVISION_DETAIL: {},
  1944.  
  1945.     // Only GET calls.
  1946.     REVISION_LIST: {},
  1947.     ROLE_DETAIL: {
  1948.       updateActionDisplay: function(type, isHtml, userAction) {
  1949.         let Model = Role;
  1950.         let entity = userAction.role;
  1951.  
  1952.         if (!entity) {
  1953.           /*
  1954.                      * Looks like population failed because the entity was deleted.
  1955.                      * Use the standard helper.
  1956.                      */
  1957.           return standardUpdateActionDisplay(
  1958.             type,
  1959.             isHtml,
  1960.             userAction,
  1961.             entity,
  1962.             Model
  1963.           );
  1964.         }
  1965.  
  1966.         switch (userAction.body.action) {
  1967.           case "update":
  1968.             /*
  1969.                          * Updating role notification preferences.
  1970.                          * Do not care about these updates for the building or entity activity feeds.
  1971.                          * We will later if we have an account-level feed.
  1972.                          */
  1973.             return "";
  1974.  
  1975.           default:
  1976.             // Updating role permissions.
  1977.             switch (type) {
  1978.               case "entity":
  1979.                 // Do not have feeds for individual roles.
  1980.                 return "";
  1981.  
  1982.               case "building": {
  1983.                 // Need to get the display role based on the perms in the body.
  1984.                 let _body = entity.toJSON();
  1985.  
  1986.                 if (userAction.body.type) {
  1987.                   _body.type = userAction.body.type;
  1988.                 }
  1989.                 let _role = new Model(_body);
  1990.  
  1991.                 return (
  1992.                   addBoldHtmlTag(isHtml, userAction.user.email) +
  1993.                   " updated the building permissions for " +
  1994.                   addBoldHtmlTag(isHtml, entity.email) +
  1995.                   " to " +
  1996.                   addBoldHtmlTag(isHtml, _role.display_role)
  1997.                 );
  1998.               }
  1999.  
  2000.               // case 'account':
  2001.               // case 'organization':
  2002.               default:
  2003.                 // Just send off the standard update message.
  2004.                 return standardUpdateActionDisplay(
  2005.                   type,
  2006.                   isHtml,
  2007.                   userAction,
  2008.                   entity,
  2009.                   Model
  2010.                 );
  2011.             }
  2012.         }
  2013.       },
  2014.       deleteActionDisplay: function(type, isHtml, userAction) {
  2015.         //  Archive / expire a role.
  2016.         let Model = Role;
  2017.         let actionHtml =
  2018.           addBoldHtmlTag(isHtml, userAction.user.email) + " removed ";
  2019.  
  2020.         // Fake create the item (without saving of course) to get the display_role.
  2021.         let _role = new Model(userAction.body);
  2022.  
  2023.         // Need to get the created role's email... but our data is really fucked up...
  2024.         let email = userAction.body.email;
  2025.  
  2026.         if (!email && _role.email) {
  2027.           email = _role.email;
  2028.         } else if (!email && userAction.role) {
  2029.           email = userAction.role.email;
  2030.         }
  2031.  
  2032.         actionHtml += addBoldHtmlTag(isHtml, email) + " from the Building";
  2033.  
  2034.         return actionHtml;
  2035.       },
  2036.     },
  2037.     ROLE_LIST: {
  2038.       createActionDisplay: function(type, isHtml, userAction) {
  2039.         let Model = Role;
  2040.         let actionHtml =
  2041.           addBoldHtmlTag(isHtml, userAction.user.email) + " invited ";
  2042.  
  2043.         // Fake create the item (without saving of course) to get the display_role.
  2044.         let _role = new Model(userAction.body);
  2045.  
  2046.         // Need to get the created role's email... but our data is really fucked up...
  2047.         let email = userAction.body.email;
  2048.  
  2049.         if (!email && _role.email) {
  2050.           email = _role.email;
  2051.         } else if (!email && userAction.role) {
  2052.           email = userAction.role.email;
  2053.         }
  2054.  
  2055.         actionHtml +=
  2056.           addBoldHtmlTag(isHtml, email) +
  2057.           " to the Building with " +
  2058.           addBoldHtmlTag(isHtml, _role.display_role) +
  2059.           " permissions";
  2060.  
  2061.         return actionHtml;
  2062.       },
  2063.     },
  2064.     ROOM_DETAIL: {
  2065.       updateActionDisplay: function(type, isHtml, userAction) {
  2066.         return routeHelpers.PIN_DETAIL.updateActionDisplay(
  2067.           type,
  2068.           isHtml,
  2069.           userAction,
  2070.           Room
  2071.         );
  2072.       },
  2073.       deleteActionDisplay: function(type, isHtml, userAction) {
  2074.         return standardDeleteActionDisplay(type, isHtml, userAction, Room);
  2075.       },
  2076.     },
  2077.     ROOM_LIST: {
  2078.       createActionDisplay: function(type, isHtml, userAction) {
  2079.         return standardCreateActionDisplay(type, isHtml, userAction, Room);
  2080.       },
  2081.     },
  2082.     // Values are not deleted through this route (deleted when parent pin field is removed from the pin type).
  2083.     ROOM_VALUE_DETAIL: {
  2084.       updateActionDisplay: function(type, isHtml, userAction) {
  2085.         return routeHelpers.PIN_VALUE_DETAIL.updateActionDisplay(
  2086.           type,
  2087.           isHtml,
  2088.           userAction,
  2089.           Room
  2090.         );
  2091.       },
  2092.     },
  2093.  
  2094.     // Values are not created through this route (created when the parent pin field is created in the pin type).
  2095.     ROOM_VALUE_LIST: {},
  2096.  
  2097.     // GET to send validation.
  2098.     SEND_VALIDATION: {},
  2099.  
  2100.     // Tags cannot be updated.
  2101.     TAG_DETAIL: {
  2102.       deleteActionDisplay: function(type, isHtml, userAction) {
  2103.         return childDeleteActionDisplay(
  2104.           type,
  2105.           isHtml,
  2106.           userAction,
  2107.           Tag,
  2108.           userAction.document,
  2109.           Document
  2110.         );
  2111.       },
  2112.     },
  2113.     TAG_LIST: {
  2114.       createActionDisplay: function(type, isHtml, userAction) {
  2115.         return childCreateActionDisplay(
  2116.           type,
  2117.           isHtml,
  2118.           userAction,
  2119.           Tag,
  2120.           userAction.document,
  2121.           Document
  2122.         );
  2123.       },
  2124.     },
  2125.  
  2126.     // Tag categories cannot be updated.
  2127.     TAG_CATEGORY_DETAIL: {
  2128.       deleteActionDisplay: function(type, isHtml, userAction) {
  2129.         return standardDeleteActionDisplay(
  2130.           type,
  2131.           isHtml,
  2132.           userAction,
  2133.           TagCategory
  2134.         );
  2135.       },
  2136.     },
  2137.     TAG_CATEGORY_LIST: {
  2138.       createActionDisplay: function(type, isHtml, userAction) {
  2139.         return standardCreateActionDisplay(
  2140.           type,
  2141.           isHtml,
  2142.           userAction,
  2143.           TagCategory
  2144.         );
  2145.       },
  2146.     },
  2147.  
  2148.     // Tag category values cannot be updated.
  2149.     TAG_VALUE_DETAIL: {
  2150.       deleteActionDisplay: function(type, isHtml, userAction) {
  2151.         return childDeleteActionDisplay(
  2152.           type,
  2153.           isHtml,
  2154.           userAction,
  2155.           TagCategoryValue,
  2156.           userAction.tag_category,
  2157.           TagCategory
  2158.         );
  2159.       },
  2160.     },
  2161.     TAG_VALUE_LIST: {
  2162.       createActionDisplay: function(type, isHtml, userAction) {
  2163.         return childCreateActionDisplay(
  2164.           type,
  2165.           isHtml,
  2166.           userAction,
  2167.           TagCategoryValue,
  2168.           userAction.tag_category,
  2169.           TagCategory
  2170.         );
  2171.       },
  2172.     },
  2173.     TASK_DETAIL: {
  2174.       updateActionDisplay: function(type, isHtml, userAction) {
  2175.         let Model = Task;
  2176.         const modelConstants = Model.constants();
  2177.         let entityAlias = modelConstants.SINGULAR;
  2178.         let nameField = modelConstants.NAME_FIELD;
  2179.         let entity = userAction.task;
  2180.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  2181.         let patchString = null;
  2182.         let name = null;
  2183.  
  2184.         if (entity && entity[nameField]) {
  2185.           name = entity[nameField];
  2186.         } else if (!entity) {
  2187.           /*
  2188.                      * Looks like population failed because the entity was deleted.
  2189.                      * Use the standard helper.
  2190.                      */
  2191.           return standardUpdateActionDisplay(
  2192.             type,
  2193.             isHtml,
  2194.             userAction,
  2195.             entity,
  2196.             Model
  2197.           );
  2198.         }
  2199.  
  2200.         switch (userAction.body.action) {
  2201.           case "update":
  2202.             actionHtml += " updated ";
  2203.  
  2204.             if (userAction.method === "PATCH") {
  2205.               // PATCH, so we can guess what was changed,
  2206.               let getAlias = ABC.MODELS.WORK_ORDER.getAlias;
  2207.               let schema = _.keys(Model.schema.paths);
  2208.               let body = _.keys(userAction.body);
  2209.               let keys = _.intersection(body, schema);
  2210.  
  2211.               patchString = getUpdateFieldString(isHtml, keys, getAlias);
  2212.             }
  2213.             break;
  2214.  
  2215.           case "cancel":
  2216.             actionHtml += " canceled ";
  2217.             break;
  2218.  
  2219.           case "complete":
  2220.             actionHtml += " completed ";
  2221.             break;
  2222.  
  2223.           case "reopen":
  2224.             actionHtml += " reopened ";
  2225.             break;
  2226.  
  2227.           case "assign":
  2228.             if (userAction.body.assignee) {
  2229.               actionHtml += " reassigned ";
  2230.             } else {
  2231.               actionHtml += " unassigned ";
  2232.             }
  2233.             break;
  2234.  
  2235.           default:
  2236.             // Send off the standard update message.
  2237.             return standardUpdateActionDisplay(
  2238.               type,
  2239.               isHtml,
  2240.               userAction,
  2241.               entity,
  2242.               Model
  2243.             );
  2244.         }
  2245.  
  2246.         switch (type) {
  2247.           case "entity":
  2248.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  2249.  
  2250.             if (patchString) {
  2251.               actionHtml += "'s " + patchString;
  2252.             }
  2253.             break;
  2254.  
  2255.           case "building":
  2256.             if (patchString) {
  2257.               actionHtml +=
  2258.                 patchString +
  2259.                 " in " +
  2260.                 addBoldHtmlTag(isHtml, entityAlias) +
  2261.                 " - " +
  2262.                 addAnchorTag(isHtml, name, entity);
  2263.             } else {
  2264.               actionHtml +=
  2265.                 addBoldHtmlTag(isHtml, entityAlias) +
  2266.                 " - " +
  2267.                 addAnchorTag(isHtml, name, entity);
  2268.             }
  2269.             break;
  2270.  
  2271.           // case 'account':
  2272.           // case 'organization':
  2273.           default:
  2274.             // Send off the standard update message.
  2275.             return standardUpdateActionDisplay(
  2276.               type,
  2277.               isHtml,
  2278.               userAction,
  2279.               entity,
  2280.               Model
  2281.             );
  2282.         }
  2283.  
  2284.         // Message was added with Task/Work Order was completed
  2285.         const messageToRequestor = userAction.body.message_to_requestor;
  2286.         if (messageToRequestor) {
  2287.           actionHtml +=
  2288.             " with message: " +
  2289.             addItalicHtmlTag(isHtml, `"${messageToRequestor}"`);
  2290.         }
  2291.  
  2292.         return actionHtml;
  2293.       },
  2294.       deleteActionDisplay: function(type, isHtml, userAction) {
  2295.         return standardDeleteActionDisplay(type, isHtml, userAction, Task);
  2296.       },
  2297.     },
  2298.     TASK_LIST: {
  2299.       createActionDisplay: function(type, isHtml, userAction) {
  2300.         /*
  2301.                  * No way to get the correct task.number (that is part of the task name) so we can't use
  2302.                  * standardCreateActionDisplay.
  2303.                  */
  2304.         let Model = Task;
  2305.         const modelConstants = Model.constants();
  2306.         let entityAlias = modelConstants.SINGULAR;
  2307.         let nameField = modelConstants.NAME_FIELD;
  2308.         let actionHtml =
  2309.           addBoldHtmlTag(isHtml, userAction.user.email) +
  2310.           " created a new " +
  2311.           addBoldHtmlTag(isHtml, entityAlias);
  2312.  
  2313.         // Fake create the item (without saving of course).
  2314.         let _entity = new Model(userAction.body);
  2315.  
  2316.         // Add the name of the item if can be pulled out of the fake entity.
  2317.         let name = _entity[nameField];
  2318.  
  2319.         if (name) {
  2320.           /*
  2321.                      * Can't add an anchor tag (do not have the id of the created thing...) so just add the name
  2322.                      * in bold.
  2323.                      */
  2324.           actionHtml += " - " + addBoldHtmlTag(isHtml, name);
  2325.         }
  2326.  
  2327.         if (_entity.is_from_request) {
  2328.           // Add an extra bit if the task was created in response to a service request.
  2329.           let _serviceRequest = {
  2330.             _id: _entity.request,
  2331.           };
  2332.  
  2333.           actionHtml +=
  2334.             " in response to a " +
  2335.             addAnchorTag(isHtml, "Service Request", _serviceRequest);
  2336.         }
  2337.  
  2338.         /*
  2339.                  * Tasks created as part of a task schedule are not created through the API so their creation will
  2340.                  * not have associated user actions.
  2341.                  */
  2342.         return actionHtml;
  2343.       },
  2344.     },
  2345.     TERMS_OF_USE: {},
  2346.     TRADE_DETAIL: {
  2347.       updateActionDisplay: function(type, isHtml, userAction) {
  2348.         let Model = Trade;
  2349.         const modelConstants = Model.constants();
  2350.         let entityAlias = modelConstants.SINGULAR;
  2351.         let nameField = modelConstants.NAME_FIELD;
  2352.         let entity = userAction.trade;
  2353.         let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email);
  2354.         let name = null;
  2355.  
  2356.         if (entity && entity[nameField]) {
  2357.           name = entity[nameField];
  2358.         } else if (!entity) {
  2359.           /*
  2360.                      * Looks like population failed because the entity was deleted.
  2361.                      * Just use the standard helper.
  2362.                      */
  2363.           return standardUpdateActionDisplay(
  2364.             type,
  2365.             isHtml,
  2366.             userAction,
  2367.             entity,
  2368.             Model
  2369.           );
  2370.         }
  2371.  
  2372.         switch (userAction.body.action) {
  2373.           case "archive":
  2374.             actionHtml += " archived ";
  2375.             break;
  2376.           case "restore":
  2377.             actionHtml += " restored ";
  2378.             break;
  2379.           case "update":
  2380.             actionHtml += " updated ";
  2381.             break;
  2382.           default:
  2383.             // Just send off the standard update message.
  2384.             return standardUpdateActionDisplay(
  2385.               type,
  2386.               isHtml,
  2387.               userAction,
  2388.               entity,
  2389.               Model
  2390.             );
  2391.         }
  2392.  
  2393.         switch (type) {
  2394.           case "entity":
  2395.             actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  2396.             break;
  2397.           case "building":
  2398.             actionHtml +=
  2399.               addBoldHtmlTag(isHtml, entityAlias) +
  2400.               " - " +
  2401.               addAnchorTag(isHtml, name, entity);
  2402.             break;
  2403.           // case 'account':
  2404.           // case 'organization':
  2405.           default:
  2406.             // Just send off the standard update message.
  2407.             return standardUpdateActionDisplay(
  2408.               type,
  2409.               isHtml,
  2410.               userAction,
  2411.               entity,
  2412.               Model
  2413.             );
  2414.         }
  2415.  
  2416.         return actionHtml;
  2417.       },
  2418.       deleteActionDisplay: function(type, isHtml, userAction) {
  2419.         return standardDeleteActionDisplay(type, isHtml, userAction, Trade);
  2420.       },
  2421.     },
  2422.     TRADE_LIST: {
  2423.       createActionDisplay: function(type, isHtml, userAction) {
  2424.         return standardCreateActionDisplay(type, isHtml, userAction, Trade);
  2425.       },
  2426.     },
  2427.     // Only GET calls.
  2428.     TRANSACTION_DETAIL: {},
  2429.  
  2430.     // Only GET calls.
  2431.     TRANSACTION_LIST: {},
  2432.     UPGRADE_BROWSER: {},
  2433.     UPLOAD: {
  2434.       // GET POST and DELETE calls for multipart uploads.
  2435.       createActionDisplay: function(type, isHtml, userAction) {
  2436.         // TODO put more info in the userAction about the files that were uploaded
  2437.         const modelConstants = ABC.MODELS.DOCUMENT;
  2438.         let entityAlias = modelConstants.SINGULAR;
  2439.         return (
  2440.           addBoldHtmlTag(isHtml, userAction.user.email) +
  2441.           " uploaded a " +
  2442.           addBoldHtmlTag(isHtml, entityAlias)
  2443.         );
  2444.       },
  2445.     },
  2446.   };
  2447.   return routeHelpers;
  2448. });
  2449.  
  2450. /**
  2451.  * Adds bold HTML tag to string.
  2452.  *
  2453.  * @function addBoldHtmlTag
  2454.  * @memberof UserActionModel.prototype
  2455.  * @private
  2456.  *
  2457.  * @param {Boolean} isHtml True when string is HTML
  2458.  * @param {String}  str    String
  2459.  *
  2460.  * @return {String} HTML string
  2461.  */
  2462. function addBoldHtmlTag(isHtml, str) {
  2463.   return isHtml ? addHtmlTag(isHtml, str, "b") : str;
  2464. }
  2465.  
  2466. /**
  2467.  * Adds italic HTML tag to string.
  2468.  *
  2469.  * @function addItalicHtmlTag
  2470.  * @memberof UserActionModel.prototype
  2471.  * @private
  2472.  *
  2473.  * @param {Boolean} isHtml True when string is HTML
  2474.  * @param {String}  str    String
  2475.  *
  2476.  * @return {String} HTML string
  2477.  */
  2478. function addItalicHtmlTag(isHtml, str) {
  2479.   return isHtml ? addHtmlTag(isHtml, str, "i") : str;
  2480. }
  2481.  
  2482. /**
  2483.  * Adds HTML tag to string.
  2484.  *
  2485.  * @function addHtmlTag
  2486.  * @memberof UserActionModel.prototype
  2487.  * @private
  2488.  *
  2489.  * @param {Boolean} isHtml True when string is HTML
  2490.  * @param {string}  str    String
  2491.  * @param {string}  tag    HTML tag to add
  2492.  *
  2493.  * @return {String} HTML string
  2494.  */
  2495. function addHtmlTag(isHtml, str, tag) {
  2496.   return isHtml ? "<" + tag + ">" + str + "</" + tag + ">" : str;
  2497. }
  2498.  
  2499. /**
  2500.  * Adds anchor tag.
  2501.  *
  2502.  * @param {Boolean} isHtml     True if content is HTML
  2503.  * @param {String}  content    Content
  2504.  * @param {Object}  model      Model
  2505.  *
  2506.  * @return {String} HTML
  2507.  */
  2508. function addAnchorTag(isHtml, content, model) {
  2509.   if (!model || model.deleted || model.is_deleted || model.expired) {
  2510.     // Can't send a link for these (will give 404), just return bold content.
  2511.     return isHtml
  2512.       ? addBoldHtmlTag(isHtml, content) + " (deleted)"
  2513.       : content + " (deleted)";
  2514.   } else if (!isHtml) {
  2515.     return content;
  2516.   }
  2517.  
  2518.   let uri = model ? model.uri : null;
  2519.  
  2520.   if (uri && (/\/pin_types$/.test(uri) || /\/pin_types\//.test(uri))) {
  2521.     // Looks like we need to open a new tab (for pin types)
  2522.     let parts = uri.split("/pin_types");
  2523.     uri = parts.join("/settings/pin_types");
  2524.     return '<a target="_blank" href="' + uri + '">' + content + "</a>";
  2525.   } else if (uri) {
  2526.     // Looks like we can render on the dashboard.
  2527.     return '<a href="' + uri + '">' + content + "</a>";
  2528.   }
  2529.   // Return bold content.
  2530.   return addBoldHtmlTag(isHtml, content);
  2531. }
  2532.  
  2533. /**
  2534.  *
  2535.  * @param  {string}  [text]            The actual text to display
  2536.  * @param  {string}  [url]             The value of the href attribute
  2537.  * @param  {Object}  [options]         Contains all the allowed options
  2538.  * @param  {string}  [options.target]  Value for the target HTML attribute
  2539.  * @param  {string}  [options.title]   Value for the title HTML attribute
  2540.  *
  2541.  * @return {string}
  2542.  */
  2543. function toHTMLAnchor(text = "", url = "", options = {}) {
  2544.   let href= `href="${url}"`;
  2545.   let target = ``;
  2546.   let title = `title="${text}"`;
  2547.  
  2548.   if (options.target) {
  2549.     target = `target="${options.target}"`;
  2550.   }
  2551.  
  2552.   if (options.title) {
  2553.     title = `title="${options.title}"`;
  2554.   }
  2555.  
  2556.   return `<a ${target} ${href} ${title}>${text}</a>`;
  2557. }
  2558.  
  2559. /**
  2560.  * Generates a standard human-readable create action display that occurred in the provided user action.
  2561.  *
  2562.  * @function standardCreateActionDisplay
  2563.  * @memberof UserActionModel.prototype
  2564.  * @private
  2565.  *
  2566.  * @param {String}     type       Building (entity N/A for creates)
  2567.  * @param {Boolean}    isHtml     If true, return an HTML string
  2568.  * @param {UserAction} userAction User action
  2569.  * @param {Object}     Model      Model
  2570.  *
  2571.  * @return {String} HTML
  2572.  */
  2573. function standardCreateActionDisplay(type, isHtml, userAction, Model) {
  2574.   const modelConstants = Model.constants();
  2575.  
  2576.   let entityAlias = modelConstants.SINGULAR;
  2577.   let actionHtml =
  2578.     addBoldHtmlTag(isHtml, userAction.user.email) +
  2579.     " created a new " +
  2580.     addBoldHtmlTag(isHtml, entityAlias);
  2581.  
  2582.   // Fake create the item (without saving of course).
  2583.   let _entity = new Model(userAction.body);
  2584.  
  2585.   // Add the name of the item if can be pulled out of the fake entity.
  2586.   let nameField = modelConstants.NAME_FIELD;
  2587.   let name = _entity[nameField];
  2588.  
  2589.   if (name) {
  2590.     /*
  2591.          * Can't add an anchor tag (do not have the id of the created thing...), so add the name in bold.
  2592.          */
  2593.     actionHtml += " - " + addBoldHtmlTag(isHtml, name);
  2594.   }
  2595.  
  2596.   return actionHtml;
  2597. }
  2598.  
  2599. /**
  2600.  * Generates a human-readable create action display for the child element in the provided user action.
  2601.  *
  2602.  * @function childCreateActionDisplay
  2603.  * @memberof UserActionModel.prototype
  2604.  * @private
  2605.  *
  2606.  * @param {String}     type             Building (entity N/A for creates)
  2607.  * @param {Boolean}    isHtml           If true, return an Html string
  2608.  * @param {UserAction} userAction       User Action
  2609.  * @param {Object}     Model            Model
  2610.  * @param {Object}     parentEntity     Parent entity
  2611.  * @param {Object}     ParentModel      Parent Model
  2612.  *
  2613.  * @return {String} Create action display
  2614.  */
  2615. function childCreateActionDisplay(
  2616.   type,
  2617.   isHtml,
  2618.   userAction,
  2619.   Model,
  2620.   parentEntity,
  2621.   ParentModel
  2622. ) {
  2623.   let actionHtml = standardCreateActionDisplay(type, isHtml, userAction, Model);
  2624.  
  2625.   // Add some info about the parent if we can.
  2626.   const parentModelConstants = ParentModel.constants();
  2627.   let parentNameField = parentModelConstants.NAME_FIELD;
  2628.   let parentName = parentEntity ? parentEntity[parentNameField] : null;
  2629.  
  2630.   if (parentName) {
  2631.     // Add the stuff about the parent and the anchor tag for the parent.
  2632.     actionHtml +=
  2633.       " in " +
  2634.       addBoldHtmlTag(isHtml, parentModelConstants.SINGULAR) +
  2635.       " - " +
  2636.       addAnchorTag(isHtml, parentName, parentEntity);
  2637.   }
  2638.  
  2639.   return actionHtml;
  2640. }
  2641.  
  2642. /**
  2643.  * Generates a standard human-readable update action display that occurred in the provided user action.
  2644.  *
  2645.  * @function standardUpdateActionDisplay
  2646.  * @memberof UserActionModel.prototype
  2647.  * @private
  2648.  *
  2649.  * @param {String}     type         Building or entity
  2650.  * @param {boolean}    isHtml       If true, return an Html string
  2651.  * @param {UserAction} userAction   User action
  2652.  * @param {Object}     entity       Entity
  2653.  * @param {Object}     Model        Model
  2654.  *
  2655.  * @return {String} Update action display
  2656.  */
  2657. function standardUpdateActionDisplay(type, isHtml, userAction, entity, Model) {
  2658.   const modelConstants = Model.constants();
  2659.   let nameField = modelConstants.NAME_FIELD;
  2660.   let entityAlias = modelConstants.SINGULAR;
  2661.   let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email) + " updated ";
  2662.  
  2663.   switch (type) {
  2664.     case "entity":
  2665.       actionHtml += "this " + addBoldHtmlTag(isHtml, entityAlias);
  2666.       break;
  2667.  
  2668.     case "building":
  2669.       if (entity && entity[nameField]) {
  2670.         actionHtml +=
  2671.           addBoldHtmlTag(isHtml, entityAlias) +
  2672.           " - " +
  2673.           addAnchorTag(isHtml, entity[nameField], entity);
  2674.       } else {
  2675.         actionHtml += "a " + addBoldHtmlTag(isHtml, entityAlias);
  2676.       }
  2677.  
  2678.       break;
  2679.     // case 'account':
  2680.     // case 'organization':
  2681.     default:
  2682.       return "";
  2683.   }
  2684.  
  2685.   return actionHtml;
  2686. }
  2687.  
  2688. /**
  2689.  * Generates a human-readable update action display for the child element in the provided user action.
  2690.  *
  2691.  * @function childUpdateActionDisplay
  2692.  * @memberof UserActionModel.prototype
  2693.  * @private
  2694.  *
  2695.  * @param {String}      type                Building or entity
  2696.  * @param {Boolean}     isHtml              If true, return an Html string
  2697.  * @param {UserAction}  userAction          User action
  2698.  * @param {Object}      entity              Entity
  2699.  * @param {Object}      Model               Model
  2700.  * @param {Object}      parentEntity        Parent entity
  2701.  * @param {Object}      ParentModel         Parent Model
  2702.  *
  2703.  * @return {String} Update action display
  2704.  */
  2705. function childUpdateActionDisplay(
  2706.   type,
  2707.   isHtml,
  2708.   userAction,
  2709.   entity,
  2710.   Model,
  2711.   parentEntity,
  2712.   ParentModel
  2713. ) {
  2714.   let actionHtml = standardUpdateActionDisplay(
  2715.     type,
  2716.     isHtml,
  2717.     userAction,
  2718.     entity,
  2719.     Model
  2720.   );
  2721.  
  2722.   switch (type) {
  2723.     case "entity":
  2724.       // Nothing to add.
  2725.       break;
  2726.  
  2727.     case "building": {
  2728.       // Add some info about the parent if we can.
  2729.       const parentModelConstants = ParentModel.constants();
  2730.       let parentNameField = parentModelConstants.NAME_FIELD;
  2731.       let parentName = parentEntity ? parentEntity[parentNameField] : null;
  2732.       if (parentName) {
  2733.         // Add the stuff about the parent and the anchor tag for the parent.
  2734.         actionHtml +=
  2735.           " in " +
  2736.           addBoldHtmlTag(isHtml, parentModelConstants.SINGULAR) +
  2737.           " - " +
  2738.           addAnchorTag(isHtml, parentName, parentEntity);
  2739.       }
  2740.       break;
  2741.     }
  2742.     // case 'account':
  2743.     // case 'organization':
  2744.     default:
  2745.       return "";
  2746.   }
  2747.  
  2748.   return actionHtml;
  2749. }
  2750.  
  2751. /**
  2752.  * Generates a standard human-readable delete action display that occurred in the provided user action.
  2753.  *
  2754.  * @function standardDeleteActionDisplay
  2755.  * @memberof UserActionModel.prototype
  2756.  * @private
  2757.  *
  2758.  * @param {String}      type         Building or entity
  2759.  * @param {boolean}     isHtml       If true, return an Html string
  2760.  * @param {UserAction}  userAction   User action
  2761.  * @param {Object}      Model        Model
  2762.  *
  2763.  * @return {String} Delete action display
  2764.  */
  2765. function standardDeleteActionDisplay(type, isHtml, userAction, Model) {
  2766.   /*
  2767.      * Entity is null in the userAction (because it was deleted).
  2768.      * There is no way that we can get the name of the entity out of the userAction.
  2769.      */
  2770.   const modelConstants = Model.constants();
  2771.   let actionHtml = addBoldHtmlTag(isHtml, userAction.user.email) + " deleted a";
  2772.  
  2773.   let entityAlias = modelConstants.SINGULAR;
  2774.  
  2775.   if (/a|e|i|o|u/i.test(entityAlias.charAt(0))) {
  2776.     actionHtml += "n ";
  2777.   } else {
  2778.     actionHtml += " ";
  2779.   }
  2780.  
  2781.   actionHtml += addBoldHtmlTag(isHtml, entityAlias);
  2782.  
  2783.   return actionHtml;
  2784. }
  2785.  
  2786. /**
  2787.  * Generates a human-readable delete action display for the child element in the provided user action.
  2788.  *
  2789.  * @function childDeleteActionDisplay
  2790.  * @memberof UserActionModel.prototype
  2791.  * @private
  2792.  *
  2793.  * @param {String}      type                Building or entity
  2794.  * @param {Boolean}     isHtml              If true, return an Html string
  2795.  * @param {UserAction}  userAction          User action
  2796.  * @param {Object}      Model               Model
  2797.  * @param {Object}      parentEntity        Parent entity
  2798.  * @param {Object}      ParentModel         ParentModel
  2799.  *
  2800.  * @return {String} Delete action display
  2801.  */
  2802. function childDeleteActionDisplay(
  2803.   type,
  2804.   isHtml,
  2805.   userAction,
  2806.   Model,
  2807.   parentEntity,
  2808.   ParentModel
  2809. ) {
  2810.   let actionHtml = standardDeleteActionDisplay(type, isHtml, userAction, Model);
  2811.  
  2812.   // Add some info about the parent if we can.
  2813.   const parentModelConstants = ParentModel.constants();
  2814.   let parentNameField = parentModelConstants.NAME_FIELD;
  2815.   let parentName = parentEntity ? parentEntity[parentNameField] : null;
  2816.  
  2817.   if (parentName) {
  2818.     // Add the stuff about the parent and the anchor tag for the parent.
  2819.     actionHtml +=
  2820.       " from " +
  2821.       addBoldHtmlTag(isHtml, parentModelConstants.SINGULAR) +
  2822.       " - " +
  2823.       addAnchorTag(isHtml, parentName, parentEntity);
  2824.   }
  2825.  
  2826.   return actionHtml;
  2827. }
  2828.  
  2829. /**
  2830.  * Generates human-readable action_display sentences provided a list of fields that were updated.
  2831.  *
  2832.  * @function getUpdateFieldString
  2833.  * @memberof UserActionModel.prototype
  2834.  * @private
  2835.  *
  2836.  * @param {Boolean} isHtml      If true, return an Html string
  2837.  * @param {Array}   keys        Array of fields that were updated
  2838.  * @param {Function}  getAlias     Function that when given a schema
  2839.  *                                 key, returns a human-readable string.
  2840.  *
  2841.  * @return {String} Update field string
  2842.  */
  2843. function getUpdateFieldString(isHtml, keys, getAlias) {
  2844.   let fieldString = "";
  2845.  
  2846.   // Cycle through the provided keys (for fields that were updated).
  2847.   for (let i = 0; i < keys.length; i++) {
  2848.     /*
  2849.          * Append the phrase with appropriate punctuation depending on the number of fields that were updated and
  2850.          * the position of this field.
  2851.          */
  2852.     let alias = getAlias(keys[i]);
  2853.     if (keys.length === 1 || i === 0) {
  2854.       // Do not include starting ' '.
  2855.       fieldString += addBoldHtmlTag(isHtml, alias);
  2856.     } else if (i < keys.length - 1) {
  2857.       fieldString += ", " + addBoldHtmlTag(isHtml, alias);
  2858.     } else {
  2859.       fieldString += " and " + addBoldHtmlTag(isHtml, alias);
  2860.     }
  2861.   }
  2862.  
  2863.   return fieldString;
  2864. }
  2865.  
  2866. // METHODS =========================================================================================================
  2867.  
  2868. // OTHER ===========================================================================================================
  2869.  
  2870. UserAction.pre("save", function preSave(next) {
  2871.   // Make sure we don't store things we aren't supposed to know.
  2872.   let blacklistedFields = [
  2873.     "password",
  2874.     "secret",
  2875.     "new_password",
  2876.     "existing_password",
  2877.     "encrypted_credentials",
  2878.     "credentials",
  2879.   ];
  2880.  
  2881.   if (this.body) {
  2882.     this.body = _.omit(this.body, blacklistedFields);
  2883.   }
  2884.  
  2885.   if (this.query) {
  2886.     this.query = _.omit(this.query, blacklistedFields);
  2887.   }
  2888.  
  2889.   return next();
  2890. });
  2891.  
  2892. if (!UserAction.options.toJSON) {
  2893.   UserAction.options.toJSON = {};
  2894. }
  2895.  
  2896. UserAction.options.toJSON.transform = function(doc, ret, options) {
  2897.   delete ret.__v;
  2898.   delete ret.ip;
  2899.  
  2900.   if (options.includeDisplayDates) {
  2901.     let utcOffset = options.utcOffset || null;
  2902.     let format = options.dateFormat || null;
  2903.  
  2904.     ret = _.extend(ret, doc.getDisplayDates(utcOffset, format));
  2905.   }
  2906.  
  2907.   ret.entity_action_display = doc.entity_action_display;
  2908.   ret.building_action_display = doc.building_action_display;
  2909.   ret.entity_action_html = doc.entity_action_html;
  2910.   ret.building_action_html = doc.building_action_html;
  2911.   ret.is_200 = doc.is_200;
  2912.   ret.is_not_200 = doc.is_not_200;
  2913.   ret.is_500 = doc.is_500;
  2914.   ret.has_user = doc.has_user;
  2915.   ret.stringified_body = doc.stringified_body;
  2916.   ret.stringified_query = doc.stringified_query;
  2917.  
  2918.   if (options && options.stringify) {
  2919.     // For the popovers in admin dashboard.
  2920.     ret.stringified = JSON.stringify(_.clone(ret));
  2921.   }
  2922. };
  2923.  
  2924. // read user actions from the secondary databases instead of the primary
  2925. UserAction.set("read", "secondaryPreferred");
  2926.  
  2927. module.exports = mongoose.model("UserAction", UserAction);
  2928.  
  2929.  
  2930.  
  2931.  
  2932.  
  2933.  
  2934.  
  2935.  
  2936.  
  2937.  
  2938.  
  2939. function a() {
  2940.   var courses = [];
  2941.   var studentRecords = openStudentEnrollmentRecords();
  2942.  
  2943.   while (studentRecords.readNextRecord()) {
  2944.     var courseNumber = studentRecords.getCourseNumber();
  2945.     var studentName = studentRecords.getStudentName();
  2946.  
  2947.     // lets say, courseNumber is 3
  2948.     if (!courses[courseNumber]) {
  2949.       // courseNumber = 3
  2950.       // courses[3] do you e
  2951.       courses[courseNumber]= [];
  2952.       // okay, courses = [null, null, null, ["bob", "jack"]]
  2953.     }
  2954.  
  2955.     maleStudents[courseNumber].push(studentName);
  2956.     femaleStudents[courseNumber].push(studentName);
  2957.   }
  2958.  
  2959.   // we should export all the names and the croues tob e storted out
  2960.   // now we just need to print
  2961.   // <table>
  2962.   // <th>.courtsers students total m/f .....
  2963.   for (var i = 18000; i < courses.length; i++) {
  2964.     var courseNumber = i;
  2965.     var students = courses[courseNumber];
  2966.  
  2967.     if (!students) {
  2968.       continue;
  2969.     }
  2970.  
  2971.     innerHtml += "<tr><td>" + courseNumber + "</td>";
  2972.     innerHtml += "<td>";
  2973.  
  2974.     for (var a = 0; a < students.length; a++) {
  2975.       var student = students[a];
  2976.  
  2977.       innerHtml += student + ", ";
  2978.     }
  2979.  
  2980.     innerHtml += "</td><td>" + students.length + "</td></tr>";
  2981.  
  2982.    
  2983.   }
  2984.   // </table>
  2985. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement