Skip to content

Commit 0f75af9

Browse files
feat: implement OOO acknowledgement functionality (#2471)
* initial commit * fix-services-ooorequest-test * fix-module-imports * refactor: streamline OOO request handling and enhance validation - Removed unnecessary checks for development mode and user roles in createOooRequestController. - Improved documentation for acknowledgeOooRequest function to clarify parameters and return types. - Added validation for request parameters in acknowledgeOooRequest middleware. - Updated error handling in getRequestById to use a constant for not found messages. - Cleaned up imports in requests model to remove unused constants. * feat: enhance OOO request creation with user role validation - Added role validation to ensure only users part of Discord can create OOO requests. - Implemented a check for development mode to restrict access to the feature. - Updated the controller to handle unauthorized access appropriately. * fix-getRequestByID-function * fix-validator-name * refactor: remove redundant request constants from REQUEST_LOG_TYPE * fix-ooo-models-condtions * refactor: enhance OOO request handling and validation - Updated createOooRequestController to use username instead of roles for user identification. - Introduced conditionalOooChecks middleware to apply role and dev checks specifically for OOO requests. - Improved validation error handling in acknowledgeOooRequest and added constants for error messages. - Refactored request validation schemas for clarity and consistency. - Cleaned up imports and ensured proper middleware usage in routes. * refactor: update error handling in OOO request service - Replaced BadRequest error with NotFound error in acknowledgeOooRequest for improved clarity. - Removed unused import of NotFound from requests model to streamline code. * fix/function-name * fix/spaces * refactor: simplify variable usage in acknowledgeOooRequest - Removed redundant variable assignments for 'from', 'until', and 'requestedBy' in acknowledgeOooRequest function. - Directly used the parameters in subsequent function calls to enhance code clarity and reduce unnecessary lines. * refactor: rename acknowledgeOooRequest for consistency - Updated the function name from acknowledgeOooRequest to acknowledgeOooRequestController for clarity and to align with naming conventions in the codebase. - Adjusted import statements accordingly in the requests controller to reflect the new function name. * refactor: update acknowledgeOooRequest to improve clarity and maintainability - Added a TODO comment to indicate the need for future removal of legacy request format handling. - Simplified the return structure by removing unnecessary data wrapping around requestResult. * refactor: enhance type handling in acknowledgeOooRequest - Updated requestData type assertion to include oldOooStatusRequest for better type safety. - Removed legacy request format normalization logic to streamline the function and improve clarity. * fix-nested-loop-refactor * fix/change-conflict-to-badrequest * refactor: improve oooRoleCheckMiddleware and oooRequests validation - Enhanced the oooRoleCheckMiddleware for better error handling and code clarity. - Updated the acknowledgeOooRequestValidator to include specific Joi validation error handling. - Simplified the validateOooAcknowledgeRequest function by removing unnecessary try-catch nesting and improving logging for invalid request types. * fix-jsdoc * refactor: improve error handling and type safety in oooRequests - Updated acknowledgeOooRequestController to remove unnecessary return statement. - Enhanced acknowledgeOooRequestValidator to streamline Joi validation error handling. - Improved type assertions in updateRequestValidator for better clarity and type safety. - Refined transformRequestResponse logic to simplify request transformation based on environment conditions. * feat: tests for OOO acknowledgement feature (#2474) * initial commit * added-more-test * fix-test-title * removed-try/catch-block * fix-try-catch-cases * added-try-catch * removed-functon * test: update assertions in oooRequest tests to use include for response validation --------- Co-authored-by: Mayank Bansal <[email protected]> --------- Co-authored-by: Mayank Bansal <[email protected]>
1 parent 749e1be commit 0f75af9

File tree

15 files changed

+549
-306
lines changed

15 files changed

+549
-306
lines changed

constants/requests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export const REQUEST_ALREADY_REJECTED = "Request already rejected";
3939
export const ERROR_WHILE_FETCHING_REQUEST = "Error while fetching request";
4040
export const ERROR_WHILE_CREATING_REQUEST = "Error while creating request";
4141
export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request";
42+
export const ERROR_WHILE_ACKNOWLEDGING_REQUEST = "Error while acknowledging request";
4243

44+
export const REQUEST_ID_REQUIRED = "Request id is required";
4345
export const REQUEST_DOES_NOT_EXIST = "Request does not exist";
4446
export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection";
4547
export const UNAUTHORIZED_TO_CREATE_OOO_REQUEST = "Unauthorized to create OOO request";

controllers/oooRequests.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ import {
88
ERROR_WHILE_UPDATING_REQUEST,
99
REQUEST_APPROVED_SUCCESSFULLY,
1010
REQUEST_REJECTED_SUCCESSFULLY,
11-
UNAUTHORIZED_TO_CREATE_OOO_REQUEST,
1211
REQUEST_ALREADY_PENDING,
1312
USER_STATUS_NOT_FOUND,
1413
OOO_STATUS_ALREADY_EXIST,
14+
UNAUTHORIZED_TO_CREATE_OOO_REQUEST,
15+
ERROR_WHILE_ACKNOWLEDGING_REQUEST,
16+
REQUEST_ID_REQUIRED,
1517
} from "../constants/requests";
1618
import { statusState } from "../constants/userStatus";
1719
import { logType } from "../constants/logs";
1820
import { addLog } from "../models/logs";
1921
import { getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
2022
import { createUserFutureStatus } from "../models/userFutureStatus";
2123
import { getUserStatus, addFutureStatus } from "../models/userStatus";
22-
import { createOooRequest, validateUserStatus } from "../services/oooRequest";
24+
import { createOooRequest, validateUserStatus, acknowledgeOooRequest as acknowledgeOooRequestService } from "../services/oooRequest";
2325
import { CustomResponse } from "../typeDefinitions/global";
24-
import { OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
26+
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
2527
import { UpdateRequest } from "../types/requests";
28+
import { NextFunction } from "express";
2629

2730
/**
2831
* Controller to handle the creation of OOO requests.
@@ -78,7 +81,7 @@ export const createOooRequestController = async (
7881
return res.boom.conflict(REQUEST_ALREADY_PENDING);
7982
}
8083

81-
await createOooRequest(requestBody, username, userId);
84+
await createOooRequest(requestBody, userId);
8285

8386
return res.status(201).json({
8487
message: REQUEST_CREATED_SUCCESSFULLY,
@@ -148,3 +151,35 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
148151
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
149152
}
150153
};
154+
/**
155+
* Acknowledges an Out-of-Office (OOO) request by updating its status to approved or rejected
156+
*
157+
* @param {AcknowledgeOooRequest} req - The request object containing acknowledgment details (status, comment) and request ID in params
158+
* @param {OooRequestResponse} res - The response object for sending success/error responses
159+
* @param {NextFunction} next - Express next function for error handling
160+
* @returns {Promise<OooRequestResponse>} Resolves with success message or passes error to next middleware
161+
*/
162+
export const acknowledgeOooRequestController = async (
163+
req: AcknowledgeOooRequest,
164+
res: OooRequestResponse,
165+
next: NextFunction
166+
)
167+
: Promise<OooRequestResponse> => {
168+
try {
169+
170+
const requestBody = req.body;
171+
const superUserId = req.userData.id;
172+
const requestId = req.params.id;
173+
174+
const response = await acknowledgeOooRequestService(requestId, requestBody, superUserId);
175+
176+
return res.status(200).json({
177+
message: response.message,
178+
});
179+
}
180+
catch(error){
181+
logger.error(ERROR_WHILE_ACKNOWLEDGING_REQUEST, error);
182+
next(error);
183+
return;
184+
}
185+
};

controllers/requests.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
} from "../constants/requests";
66
import { getRequests } from "../models/requests";
77
import { getPaginatedLink } from "../utils/helper";
8-
import { createOooRequestController, updateOooRequestController } from "./oooRequests";
9-
import { OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest";
8+
import { acknowledgeOooRequestController, createOooRequestController, updateOooRequestController } from "./oooRequests";
9+
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest";
1010
import { CustomResponse } from "../typeDefinitions/global";
1111
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests";
1212
import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extensionRequestsv2";
@@ -17,7 +17,7 @@ import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOn
1717
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
1818
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";
1919

20-
import { Request } from "express";
20+
import { Request, NextFunction } from "express";
2121

2222
export const createRequestController = async (
2323
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -121,9 +121,12 @@ export const getRequestsController = async (req: any, res: any) => {
121121
* @param {CustomResponse} res - The response object.
122122
* @returns {Promise<void>} Resolves or sends an error for invalid types.
123123
*/
124-
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
124+
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => {
125125
const type = req.body.type;
126126
switch(type){
127+
case REQUEST_TYPE.OOO:
128+
await acknowledgeOooRequestController(req as AcknowledgeOooRequest, res as OooRequestResponse, next);
129+
break;
127130
case REQUEST_TYPE.ONBOARDING:
128131
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);
129132
break;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NextFunction } from "express";
2+
import { CustomRequest, CustomResponse } from "../types/global";
3+
import { REQUEST_TYPE } from "../constants/requests";
4+
import { devFlagMiddleware } from "./devFlag";
5+
import authorizeRoles from "./authorizeRoles";
6+
7+
const { SUPERUSER } = require("../constants/roles");
8+
9+
export const oooRoleCheckMiddleware = (
10+
req: CustomRequest,
11+
res: CustomResponse,
12+
next: NextFunction
13+
) => {
14+
const requestType = req.body?.type;
15+
16+
if (requestType === REQUEST_TYPE.OOO) {
17+
// TODO: Remove this middleware once the OOO feature is tested and ready
18+
return devFlagMiddleware(req, res, (err) => {
19+
if (err) return next(err);
20+
21+
try {
22+
return authorizeRoles([SUPERUSER])(req, res, next);
23+
} catch (authErr) {
24+
return next(authErr);
25+
}
26+
});
27+
}
28+
29+
return next();
30+
};

middlewares/validators/oooRequests.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import joi from "joi";
22
import { NextFunction } from "express";
3-
import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests";
4-
import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
3+
import { REQUEST_STATE, REQUEST_TYPE, ERROR_WHILE_ACKNOWLEDGING_REQUEST } from "../../constants/requests";
4+
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
55

66
export const createOooStatusRequestValidator = async (
77
req: OooRequestCreateRequest,
@@ -38,3 +38,65 @@ export const createOooStatusRequestValidator = async (
3838

3939
await schema.validateAsync(req.body, { abortEarly: false });
4040
};
41+
42+
const acknowledgeOooRequestSchema = joi
43+
.object()
44+
.strict()
45+
.keys({
46+
comment: joi.string().optional()
47+
.messages({
48+
"string.empty": "comment cannot be empty",
49+
}),
50+
status: joi
51+
.string()
52+
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED)
53+
.required()
54+
.messages({
55+
"any.only": "status must be APPROVED or REJECTED",
56+
}),
57+
type: joi.string().equal(REQUEST_TYPE.OOO).required().messages({
58+
"any.required": "type is required",
59+
"any.only": "type must be OOO"
60+
})
61+
});
62+
63+
const paramsSchema = joi
64+
.object()
65+
.strict()
66+
.keys({
67+
id: joi.string().trim().required().messages({
68+
"any.required": "Request ID is required",
69+
"string.empty": "Request ID cannot be empty"
70+
})
71+
});
72+
73+
/**
74+
* Middleware to validate the acknowledge Out-Of-Office (OOO) request payload.
75+
*
76+
* @param {AcknowledgeOooRequest} req - The request object containing the body to be validated.
77+
* @param {OooRequestResponse} res - The response object used to send error responses if validation fails.
78+
* @param {NextFunction} next - The next middleware function to call if validation succeeds.
79+
* @returns {Promise<void>} Resolves or sends errors.
80+
*/
81+
export const acknowledgeOooRequestValidator = async (
82+
req: AcknowledgeOooRequest,
83+
res: OooRequestResponse,
84+
next: NextFunction
85+
): Promise<void> => {
86+
try {
87+
await acknowledgeOooRequestSchema.validateAsync(req.body, { abortEarly: false });
88+
await paramsSchema.validateAsync(req.params, { abortEarly: false });
89+
return next();
90+
} catch (error: unknown) {
91+
92+
if (error instanceof joi.ValidationError) {
93+
const errorMessages = error.details.map((detail) => detail.message);
94+
logger.error(`${ERROR_WHILE_ACKNOWLEDGING_REQUEST}: ${errorMessages}`);
95+
return res.boom.badRequest(errorMessages);
96+
}
97+
98+
const errorMessage = error instanceof Error ? error.message : String(error);
99+
logger.error(`${ERROR_WHILE_ACKNOWLEDGING_REQUEST}: ${errorMessage}`);
100+
return res.boom.badRequest([ERROR_WHILE_ACKNOWLEDGING_REQUEST]);
101+
}
102+
};

middlewares/validators/requests.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import joi from "joi";
22
import { NextFunction } from "express";
33
import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests";
4-
import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
5-
import { createOooStatusRequestValidator } from "./oooRequests";
4+
import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
5+
import { acknowledgeOooRequestValidator, createOooStatusRequestValidator } from "./oooRequests";
66
import { createExtensionRequestValidator } from "./extensionRequestsv2";
77
import {createTaskRequestValidator} from "./taskRequests";
88
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests";
@@ -88,7 +88,11 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
8888
.valid(REQUEST_TYPE.OOO, REQUEST_TYPE.EXTENSION, REQUEST_TYPE.TASK, REQUEST_TYPE.ALL, REQUEST_TYPE.ONBOARDING)
8989
.optional(),
9090
requestedBy: joi.string().insensitive().optional(),
91-
state: joi
91+
state: joi
92+
.string()
93+
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.PENDING, REQUEST_STATE.REJECTED)
94+
.optional(),
95+
status: joi
9296
.string()
9397
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.PENDING, REQUEST_STATE.REJECTED)
9498
.optional(),
@@ -125,13 +129,13 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
125129
/**
126130
* Validates update requests based on their type.
127131
*
128-
* @param {UpdateOnboardingExtensionRequest} req - Request object.
132+
* @param {UpdateOnboardingExtensionRequest | AcknowledgeOooRequest} req - Request object.
129133
* @param {CustomResponse} res - Response object.
130134
* @param {NextFunction} next - Next middleware if valid.
131135
* @returns {Promise<void>} Resolves or sends errors.
132136
*/
133137
export const updateRequestValidator = async (
134-
req: UpdateOnboardingExtensionRequest,
138+
req: UpdateOnboardingExtensionRequest | AcknowledgeOooRequest,
135139
res: CustomResponse,
136140
next: NextFunction
137141
): Promise<void> => {
@@ -142,6 +146,9 @@ export const updateRequestValidator = async (
142146
req,
143147
res as OnboardingExtensionResponse, next);
144148
break;
149+
case REQUEST_TYPE.OOO:
150+
await acknowledgeOooRequestValidator(req as AcknowledgeOooRequest, res as OooRequestResponse, next);
151+
break;
145152
default:
146153
return res.boom.badRequest("Invalid type");
147154
}

models/requests.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import firestore from "../utils/firestore";
2-
import type { OooStatusRequest } from "../types/oooRequest";
32
const requestModel = firestore.collection("requests");
4-
import { REQUEST_ALREADY_APPROVED, REQUEST_ALREADY_REJECTED, REQUEST_STATE, REQUEST_TYPE } from "../constants/requests";
3+
import { REQUEST_ALREADY_APPROVED, REQUEST_ALREADY_REJECTED, REQUEST_STATE } from "../constants/requests";
54
import {
65
ERROR_WHILE_FETCHING_REQUEST,
76
ERROR_WHILE_CREATING_REQUEST,

routes/requests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const router = express.Router();
33
const authorizeRoles = require("../middlewares/authorizeRoles");
44
const { SUPERUSER } = require("../constants/roles");
55
import authenticate from "../middlewares/authenticate";
6+
import { oooRoleCheckMiddleware } from "../middlewares/oooRoleCheckMiddleware";
67
import {
78
createRequestsMiddleware,
89
updateRequestsMiddleware,
@@ -22,6 +23,6 @@ import { verifyDiscordBot } from "../middlewares/authorizeBot";
2223
router.get("/", getRequestsMiddleware, getRequestsController);
2324
router.post("/", skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot), createRequestsMiddleware, createRequestController);
2425
router.put("/:id",authenticate, authorizeRoles([SUPERUSER]), updateRequestsMiddleware, updateRequestController);
25-
router.patch("/:id", authenticate, updateRequestValidator, updateRequestBeforeAcknowledgedController);
26+
router.patch("/:id", authenticate, oooRoleCheckMiddleware, updateRequestValidator, updateRequestBeforeAcknowledgedController);
2627
module.exports = router;
2728

0 commit comments

Comments
 (0)