Skip to content

Commit 7552eb1

Browse files
authored
feat: Add an API to edit onboarding extension request details before 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
1 parent 73fb9e5 commit 7552eb1

File tree

9 files changed

+327
-10
lines changed

9 files changed

+327
-10
lines changed

constants/logs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ export const logType = {
1616
EXTENSION_REQUESTS: "extensionRequests",
1717
TASK: "task",
1818
TASK_REQUESTS: "taskRequests",
19-
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
19+
USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED",
20+
REQUEST_DOES_NOT_EXIST:"REQUEST_DOES_NOT_EXIST",
21+
UNAUTHORIZED_TO_UPDATE_REQUEST: "UNAUTHORIZED_TO_UPDATE_REQUEST",
22+
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
23+
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
24+
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
2025
...REQUEST_LOG_TYPE,
2126
};
2227

constants/requests.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const REQUEST_LOG_TYPE = {
2424
REQUEST_REJECTED: "REQUEST_REJECTED",
2525
REQUEST_BLOCKED: "REQUEST_BLOCKED",
2626
REQUEST_CANCELLED: "REQUEST_CANCELLED",
27+
REQUEST_UPDATED: "REQUEST_UPDATED",
2728
};
2829

2930
export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
@@ -56,4 +57,10 @@ export const TASK_REQUEST_MESSAGES = {
5657
};
5758

5859
export const ONBOARDING_REQUEST_CREATED_SUCCESSFULLY = "Onboarding extension request created successfully"
59-
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
60+
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
61+
62+
export const PENDING_REQUEST_UPDATED = "Only pending extension request can be updated";
63+
export const INVALID_REQUEST_TYPE = "Invalid request type";
64+
export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline";
65+
export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully";
66+
export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request";

controllers/onboardingExtension.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
REQUEST_REJECTED_SUCCESSFULLY,
1111
REQUEST_STATE,
1212
REQUEST_TYPE,
13+
REQUEST_UPDATED_SUCCESSFULLY,
1314
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
15+
UNAUTHORIZED_TO_UPDATE_REQUEST,
1416
} from "../constants/requests";
1517
import { userState } from "../constants/userStatus";
1618
import { addLog } from "../services/logService";
@@ -24,10 +26,15 @@ import {
2426
OnboardingExtensionCreateRequest,
2527
OnboardingExtensionResponse,
2628
UpdateOnboardingExtensionStateRequest,
27-
UpdateOnboardingExtensionStateRequestBody
29+
UpdateOnboardingExtensionStateRequestBody,
30+
UpdateOnboardingExtensionRequest,
31+
UpdateOnboardingExtensionRequestBody
2832
} from "../types/onboardingExtension";
2933
import { convertDateStringToMilliseconds, getNewDeadline } from "../utils/requests";
3034
import { convertDaysToMilliseconds } from "../utils/time";
35+
import firestore from "../utils/firestore";
36+
import { updateOnboardingExtensionRequest, validateOnboardingExtensionUpdateRequest } from "../services/onboardingExtension";
37+
const requestModel = firestore.collection("requests");
3138

3239
/**
3340
* Controller to handle the creation of onboarding extension requests.
@@ -200,3 +207,62 @@ export const updateOnboardingExtensionRequestState = async (
200207
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
201208
}
202209
}
210+
211+
/**
212+
* Updates an onboarding extension request.
213+
*
214+
* @param {UpdateOnboardingExtensionRequest} req - The request object.
215+
* @param {OnboardingExtensionResponse} res - The response object.
216+
* @returns {Promise<OnboardingExtensionResponse>} Resolves with success or failure.
217+
*/
218+
export const updateOnboardingExtensionRequestController = async (
219+
req: UpdateOnboardingExtensionRequest,
220+
res: OnboardingExtensionResponse): Promise<OnboardingExtensionResponse> =>
221+
{
222+
223+
const body = req.body as UpdateOnboardingExtensionRequestBody;
224+
const id = req.params.id;
225+
const lastModifiedBy = req?.userData?.id;
226+
const isSuperuser = req?.userData?.roles?.super_user === true;
227+
const dev = req.query.dev === "true";
228+
229+
if(!dev) return res.boom.notImplemented("Feature not implemented");
230+
231+
try{
232+
const extensionRequestDoc = await requestModel.doc(id).get();
233+
const validationResponse = await validateOnboardingExtensionUpdateRequest(
234+
extensionRequestDoc,
235+
id,
236+
isSuperuser,
237+
lastModifiedBy,
238+
body.newEndsOn,
239+
)
240+
241+
if (validationResponse){
242+
if(validationResponse.error === REQUEST_DOES_NOT_EXIST){
243+
return res.boom.notFound(validationResponse.error);
244+
}
245+
if(validationResponse.error === UNAUTHORIZED_TO_UPDATE_REQUEST){
246+
return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST);
247+
}
248+
return res.boom.badRequest(validationResponse.error);
249+
}
250+
251+
const requestBody = await updateOnboardingExtensionRequest(
252+
id,
253+
body,
254+
lastModifiedBy,
255+
)
256+
257+
return res.status(200).json({
258+
message: REQUEST_UPDATED_SUCCESSFULLY,
259+
data: {
260+
id: extensionRequestDoc.id,
261+
...requestBody
262+
}
263+
})
264+
}catch(error){
265+
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
266+
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
267+
}
268+
}

controllers/requests.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import { UpdateRequest } from "../types/requests";
1414
import { TaskRequestRequest } from "../types/taskRequests";
1515
import { createTaskRequestController } from "./taskRequestsv2";
1616
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
17-
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
17+
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
18+
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";
19+
20+
import { Request } from "express";
1821

1922
export const createRequestController = async (
2023
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -103,3 +106,21 @@ export const getRequestsController = async (req: any, res: any) => {
103106
return res.boom.badImplementation(ERROR_WHILE_FETCHING_REQUEST);
104107
}
105108
};
109+
110+
/**
111+
* Processes update requests before acknowledgment based on type.
112+
*
113+
* @param {Request} req - The request object.
114+
* @param {CustomResponse} res - The response object.
115+
* @returns {Promise<void>} Resolves or sends an error for invalid types.
116+
*/
117+
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
118+
const type = req.body.type;
119+
switch(type){
120+
case REQUEST_TYPE.ONBOARDING:
121+
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
122+
break;
123+
default:
124+
return res.boom.badRequest("Invalid request");
125+
}
126+
}

middlewares/validators/onboardingExtensionRequest.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import joi from "joi";
22
import { NextFunction } from "express";
33
import { REQUEST_TYPE } from "../../constants/requests";
4-
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
4+
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";
55

66
export const createOnboardingExtensionRequestValidator = async (
77
req: OnboardingExtensionCreateRequest,
@@ -40,3 +40,41 @@ export const createOnboardingExtensionRequestValidator = async (
4040
throw error;
4141
}
4242
};
43+
44+
/**
45+
* Validates onboarding extension request payload.
46+
*
47+
* @param {UpdateOnboardingExtensionRequest} req - Request object.
48+
* @param {OnboardingExtensionResponse} res - Response object.
49+
* @param {NextFunction} next - Next middleware if valid.
50+
* @returns {Promise<void>} Resolves or sends errors.
51+
*/
52+
export const updateOnboardingExtensionRequestValidator = async (
53+
req: UpdateOnboardingExtensionRequest,
54+
res: OnboardingExtensionResponse,
55+
next: NextFunction): Promise<void> => {
56+
const schema = joi
57+
.object()
58+
.strict()
59+
.keys({
60+
reason: joi.string().optional(),
61+
newEndsOn: joi.number().positive().min(Date.now()).required().messages({
62+
'number.any': 'newEndsOn is required',
63+
'number.base': 'newEndsOn must be a number',
64+
'number.positive': 'newEndsOn must be positive',
65+
'number.greater': 'newEndsOn must be greater than current date',
66+
}),
67+
type: joi.string().equal(REQUEST_TYPE.ONBOARDING).required().messages({
68+
"type.any": "type is required",
69+
})
70+
});
71+
72+
try {
73+
await schema.validateAsync(req.body, { abortEarly: false });
74+
next();
75+
} catch (error) {
76+
const errorMessages = error.details.map((detail:{message: string}) => detail.message);
77+
logger.error(`Error while validating request payload : ${errorMessages}`);
78+
return res.boom.badRequest(errorMessages);
79+
}
80+
}

middlewares/validators/requests.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/e
99
import { CustomResponse } from "../../typeDefinitions/global";
1010
import { UpdateRequest } from "../../types/requests";
1111
import { TaskRequestRequest, TaskRequestResponse } from "../../types/taskRequests";
12-
import { createOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
13-
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";
12+
import { createOnboardingExtensionRequestValidator, updateOnboardingExtensionRequestValidator } from "./onboardingExtensionRequest";
13+
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionRequest } from "../../types/onboardingExtension";
1414

1515
export const createRequestsMiddleware = async (
1616
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -121,3 +121,28 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
121121
res.boom.badRequest(errorMessages);
122122
}
123123
};
124+
125+
/**
126+
* Validates update requests based on their type.
127+
*
128+
* @param {UpdateOnboardingExtensionRequest} req - Request object.
129+
* @param {CustomResponse} res - Response object.
130+
* @param {NextFunction} next - Next middleware if valid.
131+
* @returns {Promise<void>} Resolves or sends errors.
132+
*/
133+
export const updateRequestValidator = async (
134+
req: UpdateOnboardingExtensionRequest,
135+
res: CustomResponse,
136+
next: NextFunction
137+
): Promise<void> => {
138+
const type = req.body.type;
139+
switch (type) {
140+
case REQUEST_TYPE.ONBOARDING:
141+
await updateOnboardingExtensionRequestValidator(
142+
req,
143+
res as OnboardingExtensionResponse, next);
144+
break;
145+
default:
146+
return res.boom.badRequest("Invalid type");
147+
}
148+
};

routes/requests.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ import express from "express";
22
const router = express.Router();
33
const authorizeRoles = require("../middlewares/authorizeRoles");
44
const { SUPERUSER } = require("../constants/roles");
5-
65
import authenticate from "../middlewares/authenticate";
7-
import { createRequestsMiddleware,updateRequestsMiddleware,getRequestsMiddleware } from "../middlewares/validators/requests";
8-
import { createRequestController , updateRequestController, getRequestsController} from "../controllers/requests";
6+
import {
7+
createRequestsMiddleware,
8+
updateRequestsMiddleware,
9+
getRequestsMiddleware,
10+
updateRequestValidator
11+
} from "../middlewares/validators/requests";
12+
import {
13+
createRequestController ,
14+
updateRequestController,
15+
getRequestsController,
16+
updateRequestBeforeAcknowledgedController
17+
} from "../controllers/requests";
918
import { skipAuthenticateForOnboardingExtensionRequest } from "../middlewares/skipAuthenticateForOnboardingExtension";
1019
import { verifyDiscordBot } from "../middlewares/authorizeBot";
1120

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

0 commit comments

Comments
 (0)