Skip to content

Commit 4a316ed

Browse files
committed
Merge branch 'develop' of https://github.com/Real-Dev-Squad/website-backend into esm-migration
2 parents da35fad + cd928e2 commit 4a316ed

File tree

14 files changed

+945
-169
lines changed

14 files changed

+945
-169
lines changed

constants/logs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const logType = {
2222
INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE",
2323
PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED",
2424
INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE",
25+
USER_STATUS_NOT_FOUND: "USER_STATUS_NOT_FOUND",
26+
OOO_STATUS_FOUND: "OOO_STATUS_FOUND",
2527
...REQUEST_LOG_TYPE,
2628
};
2729

constants/requests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const REQUEST_LOG_TYPE = {
2525
REQUEST_BLOCKED: "REQUEST_BLOCKED",
2626
REQUEST_CANCELLED: "REQUEST_CANCELLED",
2727
REQUEST_UPDATED: "REQUEST_UPDATED",
28+
PENDING_REQUEST_FOUND: "PENDING_REQUEST_FOUND",
2829
};
2930

3031
export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully";
@@ -41,6 +42,9 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request";
4142

4243
export const REQUEST_DOES_NOT_EXIST = "Request does not exist";
4344
export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection";
45+
export const UNAUTHORIZED_TO_CREATE_OOO_REQUEST = "Unauthorized to create OOO request";
46+
export const USER_STATUS_NOT_FOUND = "User status not found";
47+
export const OOO_STATUS_ALREADY_EXIST = "Your status is already OOO. Please cancel OOO to raise new one";
4448

4549
export const TASK_REQUEST_MESSAGES = {
4650
NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request",

controllers/oooRequests.ts

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,85 @@ import {
33
LOG_ACTION,
44
REQUEST_CREATED_SUCCESSFULLY,
55
ERROR_WHILE_CREATING_REQUEST,
6-
REQUEST_ALREADY_PENDING,
76
REQUEST_STATE,
87
REQUEST_TYPE,
98
ERROR_WHILE_UPDATING_REQUEST,
109
REQUEST_APPROVED_SUCCESSFULLY,
1110
REQUEST_REJECTED_SUCCESSFULLY,
12-
} from "../constants/requests.js";
13-
import { statusState } from "../constants/userStatus.js";
14-
import { addLog } from "../models/logs.js";
15-
import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests.js";
16-
import { createUserFutureStatus } from "../models/userFutureStatus.js";
17-
import { addFutureStatus } from "../models/userStatus.js";
18-
import { CustomResponse } from "../typeDefinitions/global.js";
19-
import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest.js";
20-
import { UpdateRequest } from "../types/requests.js";
21-
import logger from "../utils/logger.js";
11+
UNAUTHORIZED_TO_CREATE_OOO_REQUEST,
12+
REQUEST_ALREADY_PENDING,
13+
USER_STATUS_NOT_FOUND,
14+
OOO_STATUS_ALREADY_EXIST,
15+
} from "../constants/requests";
16+
import { statusState } from "../constants/userStatus";
17+
import { logType } from "../constants/logs";
18+
import { addLog } from "../models/logs";
19+
import { getRequestByKeyValues, getRequests, updateRequest } from "../models/requests";
20+
import { createUserFutureStatus } from "../models/userFutureStatus";
21+
import { getUserStatus, addFutureStatus } from "../models/userStatus";
22+
import { createOooRequest, validateUserStatus } from "../services/oooRequest";
23+
import { CustomResponse } from "../typeDefinitions/global";
24+
import { OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest";
25+
import { UpdateRequest } from "../types/requests";
26+
27+
/**
28+
* Controller to handle the creation of OOO requests.
29+
*
30+
* This function processes the request to create an OOO request,
31+
* validates the user status, checks existing requests,
32+
* and stores the new request in the database with logging.
33+
*
34+
* @param {OooRequestCreateRequest} req - The Express request object containing the body with OOO details.
35+
* @param {CustomResponse} res - The Express response object used to send back the response.
36+
* @returns {Promise<OooRequestResponse>} Resolves to a response with the success or an error message.
37+
*/
38+
export const createOooRequestController = async (
39+
req: OooRequestCreateRequest,
40+
res: OooRequestResponse
41+
): Promise<OooRequestResponse> => {
2242

23-
export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => {
2443
const requestBody = req.body;
25-
const userId = req?.userData?.id;
44+
const { id: userId, username } = req.userData;
45+
const isUserPartOfDiscord = req.userData.roles.in_discord;
46+
const dev = req.query.dev === "true";
2647

27-
if (!userId) {
28-
return res.boom.unauthorized();
48+
if (!dev) return res.boom.notImplemented("Feature not implemented");
49+
50+
if (!isUserPartOfDiscord) {
51+
return res.boom.forbidden(UNAUTHORIZED_TO_CREATE_OOO_REQUEST);
2952
}
3053

3154
try {
55+
const userStatus = await getUserStatus(userId);
56+
const validationResponse = await validateUserStatus(userId, userStatus);
57+
58+
if (validationResponse) {
59+
if (validationResponse.error === USER_STATUS_NOT_FOUND) {
60+
return res.boom.notFound(validationResponse.error);
61+
}
62+
if (validationResponse.error === OOO_STATUS_ALREADY_EXIST) {
63+
return res.boom.forbidden(validationResponse.error);
64+
}
65+
}
66+
3267
const latestOooRequest: OooStatusRequest = await getRequestByKeyValues({
33-
requestedBy: userId,
34-
type: REQUEST_TYPE.OOO,
35-
state: REQUEST_STATE.PENDING,
68+
userId,
69+
type: REQUEST_TYPE.OOO,
70+
status: REQUEST_STATE.PENDING,
3671
});
3772

38-
if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) {
39-
return res.boom.badRequest(REQUEST_ALREADY_PENDING);
73+
if (latestOooRequest) {
74+
await addLog(logType.PENDING_REQUEST_FOUND,
75+
{ userId, oooRequestId: latestOooRequest.id },
76+
{ message: REQUEST_ALREADY_PENDING }
77+
);
78+
return res.boom.conflict(REQUEST_ALREADY_PENDING);
4079
}
4180

42-
const requestResult = await createRequest({ requestedBy: userId, ...requestBody });
43-
44-
const requestLog = {
45-
type: REQUEST_LOG_TYPE.REQUEST_CREATED,
46-
meta: {
47-
requestId: requestResult.id,
48-
action: LOG_ACTION.CREATE,
49-
userId: userId,
50-
createdAt: Date.now(),
51-
},
52-
body: requestResult,
53-
};
54-
await addLog(requestLog.type, requestLog.meta, requestLog.body);
81+
await createOooRequest(requestBody, username, userId);
5582

5683
return res.status(201).json({
5784
message: REQUEST_CREATED_SUCCESSFULLY,
58-
data: {
59-
id: requestResult.id,
60-
...requestResult,
61-
},
6285
});
6386
} catch (err) {
6487
logger.error(ERROR_WHILE_CREATING_REQUEST, err);

middlewares/validators/oooRequests.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ export const createOooStatusRequestValidator = async (
2626
"number.min": "until date must be greater than or equal to from date",
2727
})
2828
.required(),
29-
message: joi.string().required().messages({
30-
"any.required": "message is required",
31-
"string.empty": "message cannot be empty",
29+
reason: joi.string().required().messages({
30+
"any.required": "reason is required",
31+
"string.empty": "reason cannot be empty",
3232
}),
33-
state: joi.string().valid(REQUEST_STATE.PENDING).required().messages({
34-
"any.only": "state must be PENDING",
33+
type: joi.string().valid(REQUEST_TYPE.OOO).required().messages({
34+
"string.empty": "type cannot be empty",
35+
"any.required": "type is required",
3536
}),
36-
type: joi.string().valid(REQUEST_TYPE.OOO).required(),
3737
});
3838

3939
await schema.validateAsync(req.body, { abortEarly: false });

models/discordactions.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ const getMissedProgressUpdatesUsers = async (options = {}) => {
10201020
usersMap.set(taskAssignee, {
10211021
tasksCount: 1,
10221022
latestProgressCount: dateGap + 1,
1023-
isActive: false,
1023+
isOOO: false,
10241024
});
10251025
}
10261026
const updateTasksIdMap = async () => {
@@ -1039,10 +1039,7 @@ const getMissedProgressUpdatesUsers = async (options = {}) => {
10391039
const userIdChunks = chunks(Array.from(usersMap.keys()), FIRESTORE_IN_CLAUSE_SIZE);
10401040
const userStatusSnapshotPromise = userIdChunks.map(
10411041
async (userIdList) =>
1042-
await userStatusModel
1043-
.where("currentStatus.state", "==", userState.ACTIVE)
1044-
.where("userId", "in", userIdList)
1045-
.get()
1042+
await userStatusModel.where("currentStatus.state", "==", userState.OOO).where("userId", "in", userIdList).get()
10461043
);
10471044
const userDetailsPromise = userIdChunks.map(
10481045
async (userIdList) =>
@@ -1056,7 +1053,7 @@ const getMissedProgressUpdatesUsers = async (options = {}) => {
10561053

10571054
userStatusChunks.forEach((userStatusList) =>
10581055
userStatusList.forEach((doc) => {
1059-
usersMap.get(doc.data().userId).isActive = true;
1056+
usersMap.get(doc.data().userId).isOOO = true;
10601057
})
10611058
);
10621059

@@ -1098,7 +1095,7 @@ const getMissedProgressUpdatesUsers = async (options = {}) => {
10981095
const isDiscordMember = !!discordUserData;
10991096
const shouldAddRole =
11001097
userData.latestProgressCount === 0 &&
1101-
userData.isActive &&
1098+
!userData.isOOO &&
11021099
isDiscordMember &&
11031100
discordUserData.isDeveloper &&
11041101
!discordUserData.isMaven &&

services/oooRequest.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { logType } from "../constants/logs";
2+
import {
3+
LOG_ACTION,
4+
OOO_STATUS_ALREADY_EXIST,
5+
REQUEST_LOG_TYPE,
6+
REQUEST_STATE,
7+
USER_STATUS_NOT_FOUND,
8+
} from "../constants/requests";
9+
import { userState } from "../constants/userStatus";
10+
import { createRequest } from "../models/requests";
11+
import { OooStatusRequest, OooStatusRequestBody } from "../types/oooRequest";
12+
import { UserStatus } from "../types/userStatus";
13+
import { addLog } from "./logService";
14+
15+
/**
16+
* Validates the user status.
17+
*
18+
* @param {string} userId - The unique identifier of the user.
19+
* @param {UserStatus} userStatus - The status object of the user.
20+
* @throws {Error} Throws an error if an issue occurs during validation.
21+
*/
22+
export const validateUserStatus = async (
23+
userId: string,
24+
userStatus: UserStatus
25+
) => {
26+
try {
27+
28+
if (!userStatus.userStatusExists) {
29+
await addLog(logType.USER_STATUS_NOT_FOUND, { userId }, { message: USER_STATUS_NOT_FOUND });
30+
return {
31+
error: USER_STATUS_NOT_FOUND
32+
};
33+
}
34+
35+
if (userStatus.data.currentStatus.state === userState.OOO) {
36+
await addLog(logType.OOO_STATUS_FOUND,
37+
{ userId, userStatus: userState.OOO },
38+
{ message: OOO_STATUS_ALREADY_EXIST }
39+
);
40+
return {
41+
error: OOO_STATUS_ALREADY_EXIST
42+
};
43+
}
44+
} catch (error) {
45+
logger.error("Error while validating OOO create request", error);
46+
throw error;
47+
}
48+
}
49+
50+
/**
51+
* Create an OOO request for a user.
52+
*
53+
* @param {OooStatusRequestBody} body - The request body containing OOO details.
54+
* @param {string} username - The username of the person creating the request.
55+
* @param {string} userId - The unique identifier of the user.
56+
* @returns {Promise<object>} The created OOO request.
57+
* @throws {Error} Throws an error if an issue occurs during validation.
58+
*/
59+
export const createOooRequest = async (
60+
body: OooStatusRequestBody,
61+
username: string,
62+
userId: string
63+
) => {
64+
try {
65+
const request: OooStatusRequest = await createRequest({
66+
from: body.from,
67+
until: body.until,
68+
type: body.type,
69+
requestedBy: username,
70+
userId,
71+
reason: body.reason,
72+
comment: null,
73+
status: REQUEST_STATE.PENDING,
74+
lastModifiedBy: null,
75+
});
76+
77+
const requestLog = {
78+
type: REQUEST_LOG_TYPE.REQUEST_CREATED,
79+
meta: {
80+
requestId: request.id,
81+
action: LOG_ACTION.CREATE,
82+
userId,
83+
createdAt: Date.now(),
84+
},
85+
body: request,
86+
};
87+
88+
await addLog(requestLog.type, requestLog.meta, requestLog.body);
89+
90+
return request;
91+
} catch (error) {
92+
logger.error("Error while creating OOO request", error);
93+
throw error;
94+
}
95+
}

test/fixtures/oooRequest/oooRequest.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests.js";
1+
import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests";
2+
import { UserStatus } from "../../../types/userStatus";
23

34
export const createOooStatusRequests = {
45
type: "OOO",
@@ -15,8 +16,36 @@ export const validOooStatusRequests = {
1516
type: "OOO",
1617
from: Date.now() + 1 * 24 * 60 * 60 * 1000,
1718
until: Date.now() + 5 * 24 * 60 * 60 * 1000,
18-
message: "Out of office for personal reasons.",
19-
state: REQUEST_STATE.PENDING,
19+
reason: "Out of office for personal reasons."
20+
};
21+
22+
export const createdOOORequest = {
23+
id: "Js7JnT6uRBLjGvSJM5X5",
24+
type: validOooStatusRequests.type,
25+
from: validOooStatusRequests.from,
26+
until: validOooStatusRequests.until,
27+
reason: validOooStatusRequests.reason,
28+
status: "PENDING",
29+
lastModifiedBy: null,
30+
requestedBy: "suraj-maity-1",
31+
userId: "jCqqOYCnm93mcmaYuSsQ",
32+
comment: null
33+
};
34+
35+
export const validUserCurrentStatus = {
36+
from: Date.now(),
37+
until: Date.now() + 1 * 24 * 60 * 60 * 1000,
38+
message: "",
39+
state: "ACTIVE",
40+
updatedAt: Date.now(),
41+
};
42+
43+
export const testUserStatus: UserStatus = {
44+
id: "wcl0ZLsnngKUNZY9GkCo",
45+
data: {
46+
currentStatus: validUserCurrentStatus
47+
},
48+
userStatusExists: true
2049
};
2150

2251
export const invalidOooStatusRequests = {
@@ -129,3 +158,18 @@ export const updateOooStatusRequest = [
129158
reason: "Approval granted.",
130159
},
131160
];
161+
162+
export const createOooRequests3 = {
163+
from: Date.now() + 100000,
164+
until: Date.now() + 200000,
165+
type: "OOO",
166+
requestedBy: "suraj-maity-1",
167+
reason: "Out of office for personal emergency.",
168+
status: REQUEST_STATE.PENDING
169+
};
170+
171+
export const acknowledgeOooRequest = {
172+
type: REQUEST_TYPE.OOO,
173+
status: REQUEST_STATE.APPROVED,
174+
comment: "OOO request approved as it's emergency."
175+
};

0 commit comments

Comments
 (0)