Skip to content

Commit

Permalink
feat: Add an API to edit onboarding extension request details before …
Browse files Browse the repository at this point in the history
…approval or rejection (#2334)

* feat: Add feature to update request before approval or rejection
- Add common validator to redirect request based on type of extension
- Add type field in onboarding extension validator
- Import addLog from services to make it available for stubbing while testing
- Moved response messages to constants file
- Reuse single instance of current date in request and log model for consistent data

- Change controller name

- Remove unused variables

- Add authorization check for superuser or request ownership

- Change authorization condition

- Remove unnecessary changes

* fix: add logs for failure cases and fix check for same old and new deadline

* refactor: separate validation and update logic in service file

* chore: fix jsDoc

* fix: send id instead of while request doc while updating it

* chore: fix lint issue

* fix: change validation response condition and fix jsDoc

* fix: add strict checking

* fix: change constant message
  • Loading branch information
pankajjs authored Jan 23, 2025
1 parent 73fb9e5 commit 7552eb1
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 10 deletions.
7 changes: 6 additions & 1 deletion constants/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export const logType = {
EXTENSION_REQUESTS: "extensionRequests",
TASK: "task",
TASK_REQUESTS: "taskRequests",
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
REQUEST_DOES_NOT_EXIST:"REQUEST_DOES_NOT_EXIST",
UNAUTHORIZED_TO_UPDATE_REQUEST: "UNAUTHORIZED_TO_UPDATE_REQUEST",
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
...REQUEST_LOG_TYPE,
};

Expand Down
9 changes: 8 additions & 1 deletion constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const REQUEST_LOG_TYPE = {
REQUEST_REJECTED: "REQUEST_REJECTED",
REQUEST_BLOCKED: "REQUEST_BLOCKED",
REQUEST_CANCELLED: "REQUEST_CANCELLED",
REQUEST_UPDATED: "REQUEST_UPDATED",
};

export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
Expand Down Expand Up @@ -56,4 +57,10 @@ export const TASK_REQUEST_MESSAGES = {
};

export const ONBOARDING_REQUEST_CREATED_SUCCESSFULLY = "Onboarding extension request created successfully"
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"

export const PENDING_REQUEST_UPDATED = "Only pending extension request can be updated";
export const INVALID_REQUEST_TYPE = "Invalid request type";
export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline";
export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully";
export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request";
68 changes: 67 additions & 1 deletion controllers/onboardingExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
REQUEST_REJECTED_SUCCESSFULLY,
REQUEST_STATE,
REQUEST_TYPE,
REQUEST_UPDATED_SUCCESSFULLY,
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
UNAUTHORIZED_TO_UPDATE_REQUEST,
} from "../constants/requests";
import { userState } from "../constants/userStatus";
import { addLog } from "../services/logService";
Expand All @@ -24,10 +26,15 @@ import {
OnboardingExtensionCreateRequest,
OnboardingExtensionResponse,
UpdateOnboardingExtensionStateRequest,
UpdateOnboardingExtensionStateRequestBody
UpdateOnboardingExtensionStateRequestBody,
UpdateOnboardingExtensionRequest,
UpdateOnboardingExtensionRequestBody
} from "../types/onboardingExtension";
import { convertDateStringToMilliseconds, getNewDeadline } from "../utils/requests";
import { convertDaysToMilliseconds } from "../utils/time";
import firestore from "../utils/firestore";
import { updateOnboardingExtensionRequest, validateOnboardingExtensionUpdateRequest } from "../services/onboardingExtension";
const requestModel = firestore.collection("requests");

/**
* Controller to handle the creation of onboarding extension requests.
Expand Down Expand Up @@ -200,3 +207,62 @@ export const updateOnboardingExtensionRequestState = async (
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
}

/**
* Updates an onboarding extension request.
*
* @param {UpdateOnboardingExtensionRequest} req - The request object.
* @param {OnboardingExtensionResponse} res - The response object.
* @returns {Promise<OnboardingExtensionResponse>} Resolves with success or failure.
*/
export const updateOnboardingExtensionRequestController = async (
req: UpdateOnboardingExtensionRequest,
res: OnboardingExtensionResponse): Promise<OnboardingExtensionResponse> =>
{

const body = req.body as UpdateOnboardingExtensionRequestBody;
const id = req.params.id;
const lastModifiedBy = req?.userData?.id;
const isSuperuser = req?.userData?.roles?.super_user === true;
const dev = req.query.dev === "true";

if(!dev) return res.boom.notImplemented("Feature not implemented");

try{
const extensionRequestDoc = await requestModel.doc(id).get();
const validationResponse = await validateOnboardingExtensionUpdateRequest(
extensionRequestDoc,
id,
isSuperuser,
lastModifiedBy,
body.newEndsOn,
)

if (validationResponse){
if(validationResponse.error === REQUEST_DOES_NOT_EXIST){
return res.boom.notFound(validationResponse.error);
}
if(validationResponse.error === UNAUTHORIZED_TO_UPDATE_REQUEST){
return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST);
}
return res.boom.badRequest(validationResponse.error);
}

const requestBody = await updateOnboardingExtensionRequest(
id,
body,
lastModifiedBy,
)

return res.status(200).json({
message: REQUEST_UPDATED_SUCCESSFULLY,
data: {
id: extensionRequestDoc.id,
...requestBody
}
})
}catch(error){
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
}
}
23 changes: 22 additions & 1 deletion controllers/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { UpdateRequest } from "../types/requests";
import { TaskRequestRequest } from "../types/taskRequests";
import { createTaskRequestController } from "./taskRequestsv2";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";

import { Request } from "express";

export const createRequestController = async (
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -103,3 +106,21 @@ export const getRequestsController = async (req: any, res: any) => {
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
}
};

/**
* Processes update requests before acknowledgment based on type.
*
* @param {Request} req - The request object.
* @param {CustomResponse} res - The response object.
* @returns {Promise<void>} Resolves or sends an error for invalid types.
*/
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
const type = req.body.type;
switch(type){
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
break;
default:
return res.boom.badRequest("Invalid request");
}
}
40 changes: 39 additions & 1 deletion middlewares/validators/onboardingExtensionRequest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import joi from "joi";
import { NextFunction } from "express";
import { REQUEST_TYPE } from "../../constants/requests";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";

export const createOnboardingExtensionRequestValidator = async (
req: OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -40,3 +40,41 @@ export const createOnboardingExtensionRequestValidator = async (
throw error;
}
};

/**
* Validates onboarding extension request payload.
*
* @param {UpdateOnboardingExtensionRequest} req - Request object.
* @param {OnboardingExtensionResponse} res - Response object.
* @param {NextFunction} next - Next middleware if valid.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const updateOnboardingExtensionRequestValidator = async (
req: UpdateOnboardingExtensionRequest,
res: OnboardingExtensionResponse,
next: NextFunction): Promise<void> => {
const schema = joi
.object()
.strict()
.keys({
reason: joi.string().optional(),
newEndsOn: joi.number().positive().min(Date.now()).required().messages({
'number.any': 'newEndsOn is required',
'number.base': 'newEndsOn must be a number',
'number.positive': 'newEndsOn must be positive',
'number.greater': 'newEndsOn must be greater than current date',
}),
type: joi.string().equal(REQUEST_TYPE.ONBOARDING).required().messages({
"type.any": "type is required",
})
});

try {
await schema.validateAsync(req.body, { abortEarly: false });
next();
} catch (error) {
const errorMessages = error.details.map((detail:{message: string}) => detail.message);
logger.error(`Error while validating request payload : ${errorMessages}`);
return res.boom.badRequest(errorMessages);
}
}
29 changes: 27 additions & 2 deletions middlewares/validators/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/e
import { CustomResponse } from "../../typeDefinitions/global";
import { UpdateRequest } from "../../types/requests";
import { TaskRequestRequest, TaskRequestResponse } from "../../types/taskRequests";
import { createOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
import { createOnboardingExtensionRequestValidator, updateOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";

export const createRequestsMiddleware = async (
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
Expand Down Expand Up @@ -121,3 +121,28 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
res.boom.badRequest(errorMessages);
}
};

/**
* Validates update requests based on their type.
*
* @param {UpdateOnboardingExtensionRequest} req - Request object.
* @param {CustomResponse} res - Response object.
* @param {NextFunction} next - Next middleware if valid.
* @returns {Promise<void>} Resolves or sends errors.
*/
export const updateRequestValidator = async (
req: UpdateOnboardingExtensionRequest,
res: CustomResponse,
next: NextFunction
): Promise<void> => {
const type = req.body.type;
switch (type) {
case REQUEST_TYPE.ONBOARDING:
await updateOnboardingExtensionRequestValidator(
req,
res as OnboardingExtensionResponse, next);
break;
default:
return res.boom.badRequest("Invalid type");
}
};
18 changes: 15 additions & 3 deletions routes/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@ import express from "express";
const router = express.Router();
const authorizeRoles = require("../middlewares/authorizeRoles");
const { SUPERUSER } = require("../constants/roles");

import authenticate from "../middlewares/authenticate";
import { createRequestsMiddleware,updateRequestsMiddleware,getRequestsMiddleware } from "../middlewares/validators/requests";
import { createRequestController , updateRequestController, getRequestsController} from "../controllers/requests";
import {
createRequestsMiddleware,
updateRequestsMiddleware,
getRequestsMiddleware,
updateRequestValidator
} from "../middlewares/validators/requests";
import {
createRequestController ,
updateRequestController,
getRequestsController,
updateRequestBeforeAcknowledgedController
} from "../controllers/requests";
import { skipAuthenticateForOnboardingExtensionRequest } from "../middlewares/skipAuthenticateForOnboardingExtension";
import { verifyDiscordBot } from "../middlewares/authorizeBot";


router.get("/", getRequestsMiddleware, getRequestsController);
router.post("/", skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot), createRequestsMiddleware, createRequestController);
router.put("/:id",authenticate, authorizeRoles([SUPERUSER]), updateRequestsMiddleware, updateRequestController);
router.patch("/:id", authenticate, updateRequestValidator, updateRequestBeforeAcknowledgedController);
module.exports = router;

Loading

0 comments on commit 7552eb1

Please sign in to comment.