Source: controllers/user.controller.js

/**
 * User related CRUD operations
 * @module UserController
 * @see User
 */

const db = require("../models"),
  User = db.users,
  Item = db.items,
  {
    PUBLIC_API_KEY,
    MASTER_API_KEY,
    API_KEY_LOGIN_RADIUS,
    API_SECRET_LOGIN_RADIUS,
    SITE_NAME
  } = process.env,
  config = {
    apiDomain: 'api.loginradius.com',
    apiKey: API_KEY_LOGIN_RADIUS,
    apiSecret: API_SECRET_LOGIN_RADIUS,
    siteName: SITE_NAME,
    apiRequestSigning: false,
    proxy: {
      host: '',
      port: '',
      user: '',
      password: ''
    }
  },
  lrv2 = require('loginradius-sdk')(config);

/**
 * **Create a new User**
 * 
 * with the auth ID token and name(optional) from the request's body
 * @function create
 * @param {Object} req POST request
 * @param {Object} req.body request's body
 * @param {Number} req.body.idToken user's auth ID token
 * @param {string|undefined} [req.body.name] user's name
 * 
 * @param {Object} res response
 * @param {User} res.user created User
 */
exports.create = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return
  }
  if (apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  // Validate request parameters
  if (!req.body.idToken) {
    res
      .status(400)
      .send({ message: "Request is missing required parameters" });
    return;
  }

  // Create a User
  const name = req.body.name,
    token = req.body.idToken,
    user = new User({
      name: name,
      idToken: token
    })

  // Save User in the database
  user.save(user)
    .then(data => {
      res.send(data);
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: `Error creating User` });
    });
};

/**
 * **Retrieve all Users**
 * @function findAll
 * @param {Object} req GET request
 * 
 * @param {Object} res response
 * @param {User[]} res.data list of users
 */
exports.findAll = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  // Get query string from the Request and consider it as condition for findAll() method.
  const title = req.query.title,
    condition = title ? { title: { $regex: new RegExp(title), $options: "i" } } : {}

  User.find(condition, { __v: 0 })
    .then(data => {
      res.send(data);
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: `Error retrieving all Users` });
    });
};

/**
 * **Delete all Users**
 * @function deleteAll
 * @param {Object} req DELETE request
 * 
 * @param {Object} res response
 */
exports.deleteAll = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  User.deleteMany({})
    .then(data => {
      res.send({
        message: `${data.deletedCount} Users were deleted successfully`
      });
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: `Error deleting all Users` });
    });
};

/**
 * **Find User by auth ID token**
 * 
 * with the auth ID token in the request's path
 * @function findByIdToken
 * @param {Object} req POST request
 * @param {Object} req.params request's path parameters
 * @param {Number} req.params.idToken user's auth ID token
 * 
 * @param {Object} res response
 * @param {User} res.data found user
 */
exports.findByIdToken = async(req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  const token = req.params.token,
    profile = await getLoginRadiusProfile(token)

  if (profile) {
    const uid = profile["ID"],
      name = profile["FullName"];

    User.findOne({ idToken: uid }, { __v: 0 })
      .then(async data => {
        if (!data) {
          req.body.name = name;
          req.body.idToken = uid;
          this.create(req, res)
        } else
          res.send(data);
      })
      .catch(err => {
        res
          .status(404)
          .send({ message: `User with ID token '${token}' was not found` });
      });
  } else
    res
    .status(500)
    .send({ message: `Error retrieving User with ID token '${token}'` });
};

/**
 * **Find a single User by ID**
 * 
 * with the ID in the request's path
 * @function findOne
 * @param {{params: {id: string}}} req GET request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.id user's ID
 * 
 * @param {Object} res response
 * @param {User} res.data found user
 */
exports.findOne = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  const id = req.params.id;

  User.findById(id, { __v: 0 })
    .then(data => {
      if (!data)
        res
        .status(404)
        .send({ message: `User with ID '${id}' was not found` });
      else
        res.send(data);
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: `Error retrieving User with ID '${id}'` });
    });
};

/**
 * **Update a User by ID**
 * 
 * with the ID in the request's path and the user's details from the request's body
 * @function update
 * @param {Object} req PUT request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.id user's ID
 * @param {Object} req.body request's body
 * @param {Number|undefined} [req.body.idToken] user's auth ID token
 * @param {string|undefined} [req.body.name] user's name
 * 
 * @param {Object} res response
 * @param {string} res.message message
 */
exports.update = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  // Validate request parameters
  if (!req.body) {
    res.status(400).send({
      message: "Request is missing required parameters"
    });
    return
  }

  const id = req.params.id;

  User.findByIdAndUpdate(id, req.body, { useFindAndModify: false }, { __v: 0 })
    .then(data => {
      if (!data) {
        res.status(404).send({
          message: `Can't update User with ID '${id}'. User may not exist`
        });
      } else res.send({ message: "User was updated successfully" });
    })
    .catch(err => {
      res
        .status(500)
        .send({ message: `Error updating User with ID '${id}'` });
    });
};

/**
 * **Delete a User by ID**
 * 
 * with the ID in the request's path
 * @function delete
 * @param {Object} req DELETE request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.id user's ID
 * 
 * @param {Object} res response
 * @param {string} res.message message
 */
exports.delete = (req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  const id = req.params.id;

  User.findByIdAndRemove(id)
    .then(data => {
      if (!data) {
        res.status(404).send({
          message: `Can't delete User with ID '${id}'. User may not exist`
        });
      } else {
        res.send({
          message: "User was deleted successfully"
        });
      }
    })
    .catch(err => {
      res.status(500).send({
        message: `Error deleting User with ID '${id}'`
      });
    });
};

/**
 * **Add an Item to a User's bucket list**
 * 
 * with the user's auth token from the request's path
 * 
 * and the item's name and checked status from the request's body
 * @function addItemToUser
 * @param {Object} req POST request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.user user's auth token
 * @param {Object} req.body request's body
 * @param {string} req.body.item item's name
 * @param {boolean|undefined} [req.body.checked] item's checked status
 * 
 * @param {Object} res response
 * @param {string} res.message message
 */
exports.addItemToUser = async(req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  // Validate request parameters
  if (!req.body.item) {
    res
      .status(400)
      .send({ message: "Request is missing required parameters" });
    return
  }

  const user = req.params.user,
    itemName = req.body.item,
    itemChecked = req.body.checked,
    profile = await getLoginRadiusProfile(user),
    uid = profile["ID"]

  // Verify if item already exists in DB
  let item = await Item.findOne({ "name": itemName }),
    userHasItem;

  if (!item) {
    // Item doesn't exist yet so we create it
    const newItem = new Item({
      name: itemName
    });
    await newItem.save(newItem);

    // Retrieve the Item we just created to get its details
    // And add it to the User's bucket list
    item = await Item.findOne({ "name": itemName });
  } else {
    // Item already exists so we check if the User already has is on their bucket list
    userHasItem = await User.findOne({ "idToken": uid, "items.item": item._id });
    if (userHasItem) {
      res
        .status(403)
        .send({
          message: `User already has Item '${itemName}'`
        });
    }
  }

  // Add Item to the User's bucket list
  if (!userHasItem) {
    User.updateOne({ "idToken": uid }, { $push: { items: { item: item._id, checked: itemChecked } } }, { new: true, useFindAndModify: false })
      .then(data => {
        if (!data) {
          res.status(404).send({
            message: `Can't add Item '${itemName}' to the User. User may not exist`
          });
        } else res.send({ message: `Item '${itemName}' was added to the User successfully` });
      })
      .catch(err => {
        res.status(500).send({
          message: `Error adding Item '${itemName}' to the User`
        });
      });
  }
};

/**
 * **Update an item from a User with token**
 * 
 * with the user's auth ID token and the item's name on the request's path
 * 
 * and the item's checked status on the request's body
 * @function updateItemFromUser
 * @param {Object} req PUT request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.user user's auth token
 * @param {string} req.params.item item's name
 * @param {Object} req.body request's body
 * @param {boolean} req.body.checked item's checked status
 * 
 * @param {Object} res response
 * @param {string} res.message message
 */
exports.updateItemFromUser = async(req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  // Validate request parameters
  if (!req.body) {
    res
      .status(400)
      .send({ message: "Request is missing required parameters" });
    return
  }

  const user = req.params.user,
    itemName = req.params.item,
    itemChecked = req.body.checked,
    profile = await getLoginRadiusProfile(user),
    uid = profile["ID"],
    item = await Item.findOne({ "name": itemName })

  if (item) {
    User.updateOne({ "idToken": uid, "items.item": item._id }, { "$set": { "items.$.checked": itemChecked } })
      .then(data => {
        if (!data) {
          res.status(404).send({
            message: `Can't update Item '${itemName}' from the User. User may not exist`
          });
        } else res.send({ message: `Item '${itemName}' from the User was updated successfully` });
      })
      .catch(err => {
        res.status(500)
          .send({ message: `Error updating Item '${item}' from the User` });
      });
  }
}

/**
 * **Delete an item from a User with token**
 * 
 * with the user's auth ID token and the item's name on the request's path
 * @function deleteItemFromUser
 * @param {Object} req DELETE request
 * @param {Object} req.params request's path parameters
 * @param {string} req.params.user user's auth token
 * @param {string} req.params.item item's name
 * 
 * @param {Object} res response
 * @param {string} res.message message
 */
exports.deleteItemFromUser = async(req, res) => {
  // Validate authentication and authorization
  const apiKey = req.header('x-api-key')
  if (!apiKey) {
    res
      .status(401)
      .send({ message: "Missing authentication header" });
    return;
  }
  if (apiKey !== PUBLIC_API_KEY && apiKey !== MASTER_API_KEY) {
    res
      .status(403)
      .send({ message: "You have no authorization to complete this operation" });
    return
  }

  const user = req.params.user,
    itemName = req.params.item,
    profile = await getLoginRadiusProfile(user),
    uid = profile["ID"],
    item = await Item.findOne({ "name": itemName })

  if (item) {
    User.updateOne({ "idToken": uid }, { $pull: { items: { item: item._id } } })
      .then(data => {
        if (!data) {
          res.status(404).send({
            message: `Can't delete Item ${itemName} from the User. User may not exist`
          });
        } else res.send({ message: `Item '${itemName}' from the User was deleted successfully` });
      })
      .catch(err => {
        res.status(500).send({
          message: `Error deleting Item '${itemName}' from the User`
        });
      });
  }
};

/**
 * **Get User profile from Login Radius**
 * @function getLoginRadiusProfile
 * @param {string} token User's token
 * @returns {Object|null} User's profile
 */
async function getLoginRadiusProfile(token) {
  return await lrv2.authenticationApi.getProfileByAccessToken(token)
    .then(data =>
      (!data) ? null : data["Identities"][0]
    )
    .catch((error) => null);
}