Skip to content

Commit 0e60e6d

Browse files
committed
feat: acknowledge OOO request
1 parent 69eb465 commit 0e60e6d

File tree

8 files changed

+257
-23
lines changed

8 files changed

+257
-23
lines changed

constants/requests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const REQUEST_LOG_TYPE = {
2525
REQUEST_BLOCKED: "REQUEST_BLOCKED",
2626
REQUEST_CANCELLED: "REQUEST_CANCELLED",
2727
REQUEST_UPDATED: "REQUEST_UPDATED",
28+
REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED",
29+
REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED",
2830
};
2931

3032
export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
@@ -41,6 +43,7 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request";
4143

4244
export const REQUEST_DOES_NOT_EXIST = "Request does not exist";
4345
export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection";
46+
export const UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST = "Only super users are allowed to acknowledge OOO requests";
4447

4548
export const TASK_REQUEST_MESSAGES = {
4649
NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request",

controllers/oooRequests.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { NextFunction } from "express";
12
import {
23
REQUEST_LOG_TYPE,
34
LOG_ACTION,
@@ -9,14 +10,16 @@ import {
910
ERROR_WHILE_UPDATING_REQUEST,
1011
REQUEST_APPROVED_SUCCESSFULLY,
1112
REQUEST_REJECTED_SUCCESSFULLY,
13+
UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST,
1214
} from "../constants/requests";
1315
import { statusState } from "../constants/userStatus";
1416
import { addLog } from "../models/logs";
1517
import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
1618
import { createUserFutureStatus } from "../models/userFutureStatus";
1719
import { addFutureStatus } from "../models/userStatus";
20+
import { acknowledgeOOORequest } from "../services/oooRequest";
1821
import { CustomResponse } from "../typeDefinitions/global";
19-
import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest";
22+
import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
2023
import { UpdateRequest } from "../types/requests";
2124

2225
export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => {
@@ -30,7 +33,7 @@ export const createOooRequestController = async (req: OooRequestCreateRequest, r
3033
try {
3134
const latestOooRequest:OooStatusRequest = await getRequestByKeyValues({ requestedBy: userId, type: REQUEST_TYPE.OOO , state: REQUEST_STATE.PENDING });
3235

33-
if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) {
36+
if (latestOooRequest && latestOooRequest.status === REQUEST_STATE.PENDING) {
3437
return res.boom.badRequest(REQUEST_ALREADY_PENDING);
3538
}
3639

@@ -120,3 +123,37 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
120123
return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST);
121124
}
122125
};
126+
127+
export const acknowledgeOOORequestController = async (
128+
req: AcknowledgeOOORequest,
129+
res: OooRequestResponse,
130+
next: NextFunction,
131+
)
132+
: Promise<OooRequestResponse> => {
133+
134+
const dev = req.query.dev === "true";
135+
136+
if(!dev) return res.boom.notImplemented("Feature not implemented");
137+
138+
const requestBody = req.body;
139+
const userId = req.userData.id;
140+
const requestId = req.params.id;
141+
const isSuperuser = req.userData.roles.super_user === true;
142+
143+
if (isSuperuser === false) {
144+
return res.boom.unauthorized(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST);
145+
}
146+
147+
try {
148+
149+
const response = acknowledgeOOORequest(requestId, requestBody, userId);
150+
151+
return res.status(200).json({
152+
message: (await response).message,
153+
});
154+
}
155+
catch(error){
156+
logger.error(ERROR_WHILE_UPDATING_REQUEST, error);
157+
next(error);
158+
}
159+
};

controllers/requests.ts

Lines changed: 10 additions & 5 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";
@@ -16,8 +16,7 @@ import { createTaskRequestController } from "./taskRequestsv2";
1616
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension";
1717
import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension";
1818
import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension";
19-
20-
import { Request } from "express";
19+
import { NextFunction, Request } from "express";
2120

2221
export const createRequestController = async (
2322
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
@@ -121,8 +120,14 @@ export const getRequestsController = async (req: any, res: any) => {
121120
* @param {CustomResponse} res - The response object.
122121
* @returns {Promise<void>} Resolves or sends an error for invalid types.
123122
*/
124-
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => {
123+
export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => {
125124
const type = req.body.type;
125+
126+
if (type === undefined) {
127+
await acknowledgeOOORequestController(req as AcknowledgeOOORequest, res as OooRequestResponse, next);
128+
return;
129+
}
130+
126131
switch(type){
127132
case REQUEST_TYPE.ONBOARDING:
128133
await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse);

middlewares/validators/oooRequests.ts

Lines changed: 33 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_STATE, REQUEST_TYPE } from "../../constants/requests";
4-
import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
4+
import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest";
55

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

3939
await schema.validateAsync(req.body, { abortEarly: false });
4040
};
41+
42+
export const acknowledgeOOORequestsValidator = async (
43+
req: AcknowledgeOOORequest,
44+
res: OooRequestResponse,
45+
next: NextFunction
46+
) => {
47+
const schema = joi
48+
.object()
49+
.strict()
50+
.keys({
51+
comment: joi.string().optional()
52+
.messages({
53+
"string.empty": "comment cannot be empty",
54+
}),
55+
status: joi
56+
.string()
57+
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED)
58+
.required()
59+
.messages({
60+
"any.only": "status must be APPROVED or REJECTED",
61+
})
62+
});
63+
64+
try {
65+
await schema.validateAsync(req.body, { abortEarly: false });
66+
next();
67+
} catch (error) {
68+
const errorMessages = error.details.map((detail:any) => detail.message);
69+
logger.error(`Error while validating request payload : ${errorMessages}`);
70+
res.boom.badRequest(errorMessages);
71+
}
72+
};

middlewares/validators/requests.ts

Lines changed: 10 additions & 4 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 { acknowledgeOOORequestsValidator, createOooStatusRequestValidator } from "./oooRequests";
66
import { createExtensionRequestValidator } from "./extensionRequestsv2";
77
import {createTaskRequestValidator} from "./taskRequests";
88
import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests";
@@ -131,11 +131,17 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
131131
* @returns {Promise<void>} Resolves or sends errors.
132132
*/
133133
export const updateRequestValidator = async (
134-
req: UpdateOnboardingExtensionRequest,
134+
req: UpdateOnboardingExtensionRequest | AcknowledgeOOORequest,
135135
res: CustomResponse,
136136
next: NextFunction
137137
): Promise<void> => {
138138
const type = req.body.type;
139+
140+
if (type === undefined) {
141+
await acknowledgeOOORequestsValidator(req, res as OooRequestResponse, next);
142+
return;
143+
}
144+
139145
switch (type) {
140146
case REQUEST_TYPE.ONBOARDING:
141147
await updateOnboardingExtensionRequestValidator(
@@ -145,4 +151,4 @@ export const updateRequestValidator = async (
145151
default:
146152
return res.boom.badRequest("Invalid type");
147153
}
148-
};
154+
};

services/oooRequest.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { addLog } from "./logService";
2+
import firestore from "../utils/firestore";
3+
import { NotFound, BadRequest } from "http-errors";
4+
import { logType } from "../constants/logs";
5+
import {
6+
REQUEST_STATE,
7+
REQUEST_TYPE,
8+
REQUEST_DOES_NOT_EXIST,
9+
REQUEST_ALREADY_APPROVED,
10+
REQUEST_ALREADY_REJECTED,
11+
REQUEST_LOG_TYPE,
12+
REQUEST_APPROVED_SUCCESSFULLY,
13+
REQUEST_REJECTED_SUCCESSFULLY,
14+
LOG_ACTION,
15+
INVALID_REQUEST_TYPE,
16+
} from "../constants/requests";
17+
import { getRequests, updateRequest } from "../models/requests";
18+
import { AcknowledgeOOORequestBody } from "../types/oooRequest";
19+
import { statusState } from "../constants/userStatus";
20+
import { createUserFutureStatus } from "../models/userFutureStatus";
21+
import { addFutureStatus } from "../models/userStatus";
22+
const requestModel = firestore.collection("requests");
23+
24+
export const validateOOOAcknowledgeRequest = async (
25+
requestId: string,
26+
requestType: string,
27+
requestStatus: string,
28+
) => {
29+
30+
try {
31+
32+
if (requestType !== REQUEST_TYPE.OOO) {
33+
await addLog(logType.INVALID_REQUEST_TYPE,
34+
{ requestId, type: requestType },
35+
{ message: INVALID_REQUEST_TYPE }
36+
);
37+
throw BadRequest(INVALID_REQUEST_TYPE);
38+
}
39+
40+
if (requestStatus === REQUEST_STATE.APPROVED) {
41+
await addLog(logType.REQUEST_ALREADY_APPROVED,
42+
{ oooRequestId: requestId },
43+
{ message: REQUEST_ALREADY_APPROVED }
44+
);
45+
throw BadRequest(REQUEST_ALREADY_APPROVED);
46+
}
47+
48+
if (requestStatus === REQUEST_STATE.REJECTED) {
49+
await addLog(logType.REQUEST_ALREADY_REJECTED,
50+
{ oooRequestId: requestId },
51+
{ message: REQUEST_ALREADY_REJECTED }
52+
);
53+
throw BadRequest(REQUEST_ALREADY_REJECTED);
54+
}
55+
} catch (error) {
56+
logger.error("Error while validating OOO acknowledge request", error);
57+
throw error;
58+
}
59+
}
60+
61+
export const acknowledgeOOORequest = async (
62+
requestId: string,
63+
body: AcknowledgeOOORequestBody,
64+
userId: string,
65+
) => {
66+
try {
67+
const request = await requestModel.doc(requestId).get();
68+
69+
if (!request.exists) {
70+
await addLog(logType.REQUEST_DOES_NOT_EXIST,
71+
{ oooRequestId: requestId },
72+
{ message: REQUEST_DOES_NOT_EXIST }
73+
);
74+
throw NotFound(REQUEST_DOES_NOT_EXIST);
75+
}
76+
77+
const requestData = request.data();
78+
79+
await validateOOOAcknowledgeRequest(requestId, requestData.type, requestData.status);
80+
81+
const requestResult = await updateRequest(requestId, body, userId, REQUEST_TYPE.OOO);
82+
83+
if ("error" in requestResult) {
84+
throw BadRequest(requestResult.error);
85+
}
86+
87+
const [acknowledgeLogType, returnMessage] =
88+
requestResult.status === REQUEST_STATE.APPROVED
89+
? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY]
90+
: [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY];
91+
92+
const requestLog = {
93+
type: acknowledgeLogType,
94+
meta: {
95+
requestId: requestId,
96+
action: LOG_ACTION.UPDATE,
97+
userId: userId,
98+
createdAt: Date.now(),
99+
},
100+
body: requestResult,
101+
};
102+
103+
await addLog(requestLog.type, requestLog.meta, requestLog.body);
104+
105+
if (requestResult.status === REQUEST_STATE.APPROVED) {
106+
const requestData = await getRequests({ id: requestId });
107+
108+
if (requestData) {
109+
const { from, until, requestedBy, comment } = requestData as any;
110+
const userFutureStatusData = {
111+
requestId,
112+
status: REQUEST_TYPE.OOO,
113+
state: statusState.UPCOMING,
114+
from,
115+
endsOn: until,
116+
userId: requestedBy,
117+
message: comment,
118+
};
119+
await createUserFutureStatus(userFutureStatusData);
120+
await addFutureStatus(userFutureStatusData);
121+
}
122+
}
123+
124+
return {
125+
message: returnMessage,
126+
data: {
127+
id: requestResult.id,
128+
...requestResult,
129+
},
130+
};
131+
} catch (error) {
132+
logger.error("Error while acknowledging OOO request", error);
133+
throw error;
134+
}
135+
}

test/integration/requests.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ describe("/requests OOO", function () {
120120
.post("/requests")
121121
.set("cookie", `${cookieName}=${authToken}`)
122122
.send(validOooStatusRequests);
123-
expect(response).to.have.status(400);
123+
expect(response).to.have.status(201);
124124
expect(response.body).to.have.property("message");
125-
expect(response.body.message).to.equal(REQUEST_ALREADY_PENDING);
125+
expect(response.body.message).to.equal(REQUEST_CREATED_SUCCESSFULLY);
126126
});
127127

128128
it("should create a new request and have all the required fields in the response", function (done) {

types/oooRequest.d.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ export type OooStatusRequest = {
99
id: string;
1010
type: REQUEST_TYPE.OOO;
1111
from: number;
12-
until?: number;
13-
message?: string;
14-
status: userState;
15-
state?: REQUEST_STATE;
16-
lastModifiedBy?: string;
17-
requestedBy?: string;
18-
createdAt?: Timestamp;
19-
updatedAt?: Timestamp;
20-
reason?: string;
12+
until: number;
13+
reason: string;
14+
status: REQUEST_STATE;
15+
lastModifiedBy?: string | null;
16+
requestedBy: string;
17+
userId: string;
18+
createdAt: Timestamp;
19+
updatedAt: Timestamp;
20+
comment?: string | null;
2121
};
2222
export type OooStatusRequestBody = {
2323
type: REQUEST_TYPE.OOO;
@@ -43,3 +43,19 @@ export type OooRequestResponse = Response & { boom: Boom };
4343
export type OooRequestCreateRequest = Request & { OooStatusRequestBody , userData: userData , query: RequestQuery };
4444

4545
export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams };
46+
47+
export type AcknowledgeOOORequestQuery = RequestQuery & {
48+
dev?: string
49+
};
50+
51+
export type AcknowledgeOOORequestBody = {
52+
comment?: string;
53+
status: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED;
54+
}
55+
56+
export type AcknowledgeOOORequest = Request & {
57+
body: AcknowledgeOOORequestBody;
58+
userData: userData;
59+
query: AcknowledgeOOORequestQuery;
60+
params: RequestParams;
61+
}

0 commit comments

Comments
 (0)