express-api/src/controllers/users/usersController.ts

Summary

Maintainability
A
0 mins
Test Coverage
import userServices from '@/services/users/usersServices';
import { Request, Response } from 'express';
import { SSOUser } from '@bcgov/citz-imb-sso-express';
import { UserFiltering, UserFilteringSchema } from '@/controllers/users/usersSchema';
import { z } from 'zod';
import notificationServices from '@/services/notifications/notificationServices';
import getConfig from '@/constants/config';
import logger from '@/utilities/winstonLogger';
import { Roles } from '@/constants/roles';

/**
 * @description Submits a user access request.
 * @param {Request}     req Incoming request.
 * @param {Response}    res Outgoing response.
 * @returns {Response}      A 201 status with the created access request.
 */
export const submitUserAccessRequest = async (req: Request, res: Response) => {
  const result = await userServices.addKeycloakUserOnHold(
    req.user as SSOUser,
    Number(req.body.AgencyId),
    req.body.Position,
    req.body.Note,
    req.body.Email,
  );
  const config = getConfig();
  const user = await userServices.getUser(req.user.preferred_username);
  //Note: old PIMS has code that suggests administrator users should be enumerated here and sent notifications.
  //However, current POs no longer desire this functionality. Just the one notification to the common RPD mailbox is fine.
  try {
    const notifRPD = await notificationServices.generateAccessRequestNotification(
      {
        FirstName: user.FirstName,
        LastName: user.LastName,
      },
      config.accessRequest.notificationTemplateRPD,
    );
    await notificationServices.sendNotification(notifRPD, req.pimsUser);
  } catch (e) {
    logger.error(`Failed to deliver access request notification: ${e.message}`);
  }

  return res.status(201).send(result);
};

/**
 * @description Gets user agency information.
 * @param {Request}     req Incoming request.
 * @param {Response}    res Outgoing response.
 * @returns {Response}      A 200 status with an array of integers.
 */
export const getUserAgencies = async (req: Request, res: Response) => {
  const user = String(req.params?.username);
  const result = await userServices.getAgencies(user);
  return res.status(200).send(result);
};

/**
 * Retrieves the user information for the currently authenticated user.
 * Normalizes the Keycloak user using internal organization's method.
 * Retrieves the user details based on the normalized username.
 * If user details are found, returns a status of 200 with the user information.
 * If no user details are found, returns a status of 204 indicating a valid request but no user for the Keycloak login.
 *
 * @param {Request} req - The request object containing user information.
 * @param {Response} res - The response object to send back the user details or status.
 * @returns {Response}        A 200 status with user's info.
 */
export const getSelf = async (req: Request, res: Response) => {
  const user = userServices.normalizeKeycloakUser(req.user as SSOUser);
  const result = await userServices.getUser(user.username);
  if (result) {
    return res.status(200).send(result);
  } else {
    return res.status(204).send(); //Valid request, but no user for this keycloak login.
  }
};

/**
 * @description Gets a paged list of users.
 * @param   {Request}     req Incoming request
 * @param   {Response}    res Outgoing response
 * @returns {Response}        A 200 status with a list of users.
 */
export const getUsers = async (req: Request, res: Response) => {
  const user = req.pimsUser;
  const filter = UserFilteringSchema.safeParse(req.query);
  if (!filter.success) {
    return res.status(400).send('Failed to parse filter query.');
  }
  const filterResult = filter.data;

  let users;
  const isAdmin = user.hasOneOfRoles([Roles.ADMIN]);
  if (isAdmin) {
    users = await userServices.getUsers(filterResult as UserFiltering);
  } else {
    // Get agencies associated with the requesting user
    const usersAgencies = await userServices.getAgencies(user.Username);
    filterResult.agencyId = usersAgencies;
    users = await userServices.getUsers(filterResult as UserFiltering);
  }
  return res.status(200).send(users);
};

/**
 * @description Gets a single user that matches an ID.
 * @param   {Request}     req Incoming request
 * @param   {Response}    res Outgoing response
 * @returns {Response}        A 200 status and the user data.
 */
export const getUserById = async (req: Request, res: Response) => {
  const id = req.params.id;
  const uuid = z.string().uuid().safeParse(id);
  const pimsUser = req.pimsUser;
  if (uuid.success) {
    const user = await userServices.getUserById(uuid.data);
    if (user) {
      const isAdmin = pimsUser.hasOneOfRoles([Roles.ADMIN]);
      if (!isAdmin) {
        // check if user has the correct agencies
        const usersAgencies = await userServices.hasAgencies(user.Username, [user.AgencyId]);
        if (!usersAgencies) {
          return res.status(403).send('User does not have permission to view this user');
        }
      }
      return res.status(200).send(user);
    } else {
      return res.status(404).send('User not found');
    }
  } else {
    return res.status(400).send('Could not parse UUID.');
  }
};

/**
 * @description Updates a single user that matches an ID.
 * @param   {Request}     req Incoming request
 * @param   {Response}    res Outgoing response
 * @returns {Response}        A 200 status and the user data.
 */
export const updateUserById = async (req: Request, res: Response) => {
  const idParse = z.string().uuid().safeParse(req.params.id);
  if (!idParse.success) {
    return res.status(400).send(idParse);
  }
  if (idParse.data != req.body.Id) {
    return res.status(400).send('The param ID does not match the request body.');
  }
  const user = await userServices.updateUser(req.body);
  return res.status(200).send(user);
};