express-api/src/controllers/parcels/parcelsController.ts
import { Request, Response } from 'express';
import parcelServices from '@/services/parcels/parcelServices';
import { ParcelFilter, ParcelFilterSchema } from '@/services/parcels/parcelSchema';
import userServices from '@/services/users/usersServices';
import { Parcel } from '@/typeorm/Entities/Parcel';
import { Roles } from '@/constants/roles';
import { checkUserAgencyPermission } from '@/utilities/authorizationChecks';
import { AppDataSource } from '@/appDataSource';
import { ProjectProperty } from '@/typeorm/Entities/ProjectProperty';
import { exposedProjectStatuses } from '@/constants/projectStatus';
/**
* @description Gets information about a particular parcel by the Id provided in the URL parameter.
* @param {Request} req Incoming Request
* @param {Response} res Outgoing Response
* @returns {Response} A 200 status with a response body containing parcel data.
*/
export const getParcel = async (req: Request, res: Response) => {
const parcelId = Number(req.params.parcelId);
if (isNaN(parcelId)) {
return res.status(400).send('Parcel ID was invalid.');
}
// admin and auditors are permitted to see any parcel
const permittedRoles = [Roles.ADMIN, Roles.AUDITOR];
const user = req.pimsUser;
const parcel = await parcelServices.getParcelById(parcelId);
if (!parcel) {
return res.status(404).send('Parcel matching this internal ID not found.');
}
// Get related projects
const projects = (
await AppDataSource.getRepository(ProjectProperty).find({
where: {
ParcelId: parcel.Id,
},
relations: {
Project: true,
},
})
).map((pp) => pp.Project);
// Are any related projects in ERP? If so, they should be visible to outside agencies.
const isVisibleToOtherAgencies = projects.some((project) =>
exposedProjectStatuses.includes(project.StatusId),
);
if (
!(await checkUserAgencyPermission(user, [parcel.AgencyId], permittedRoles)) &&
!isVisibleToOtherAgencies
) {
return res.status(403).send('You are not authorized to view this parcel.');
}
return res.status(200).send(parcel);
};
/**
* @description Updates information about a particular parcel by the Id provided in the URL parameter.
* @param {Request} req Incoming Request. Should contain complete parcel in request body.
* @param {Response} res Outgoing Response
* @returns {Response} A 200 status with a response body containing parcel data.
*/
export const updateParcel = async (req: Request, res: Response) => {
const parcelId = Number(req.params.parcelId);
if (isNaN(parcelId) || parcelId !== req.body.Id) {
return res.status(400).send('Parcel ID was invalid or mismatched with body.');
}
const user = req.pimsUser;
const updateBody = { ...req.body, UpdatedById: user.Id };
const parcel = await parcelServices.updateParcel(updateBody, req.pimsUser);
if (!parcel) {
return res.status(404).send('Parcel matching this internal ID not found.');
}
return res.status(200).send(parcel);
};
/**
* @description Deletes a particular parcel by the Id provided in the URL parameter.
* @param {Request} req Incoming Request (Note: The original implementation requires a full parcel request body, but it seems unnecessary)
* @param {Response} res Outgoing Response
* @returns {Response} A 200 status with a response body containing parcel data.
*/
export const deleteParcel = async (req: Request, res: Response) => {
const parcelId = Number(req.params.parcelId);
if (isNaN(parcelId)) {
return res.status(400).send('Parcel ID was invalid.');
}
const delResult = await parcelServices.deleteParcelById(parcelId, req.pimsUser);
return res.status(200).send(delResult);
};
/**
* @description Gets all parcels satisfying the filter parameters.
* @param {Request} req Incoming Request. May contain query strings for filter.
* @param {Response} res Outgoing Response
* @returns {Response} A 200 status with a response body containing an array of parcel data.
*/
export const getParcels = async (req: Request, res: Response) => {
const filter = ParcelFilterSchema.safeParse(req.query);
const includeRelations = req.query.includeRelations === 'true';
if (!filter.success) {
return res.status(400).send('Could not parse filter.');
}
const filterResult = filter.data;
const user = req.pimsUser;
if (!user.hasOneOfRoles([Roles.ADMIN, Roles.AUDITOR])) {
// get array of user's agencies
const usersAgencies = await userServices.getAgencies(user.Username);
filterResult.agencyId = usersAgencies;
}
// Get parcels associated with agencies of the requesting user
const parcels = await parcelServices.getParcels(filterResult as ParcelFilter, includeRelations);
return res.status(200).send(parcels);
};
/* Perhaps the above two methods could be consolidated into one?
In the original implementation they are separated into a GET and POST endpoint, but obviously
a POST endpoint could accept both query strings and request body. Whether that's RESTful or not
is another discussion though.
*/
/**
* @description Add a new parcel to the datasource for the current user.
* @param {Request} req Incoming Request. Body should contain parcel data.
* @param {Response} res Outgoing Response
* @returns {Response} A 201 status with a response body containing the created parcel data.
* Note: the original implementation returns 200, but as a resource is created 201 is better.
*/
export const addParcel = async (req: Request, res: Response) => {
const user = req.pimsUser;
const parcel: Parcel = { ...req.body, CreatedById: user.Id };
parcel.Evaluations = parcel.Evaluations?.map((evaluation) => ({
...evaluation,
CreatedById: user.Id,
}));
parcel.Fiscals = parcel.Fiscals?.map((fiscal) => ({ ...fiscal, CreatedById: user.Id }));
const response = await parcelServices.addParcel(parcel);
return res.status(201).send(response);
};