Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add api to create onboarding extension request from discord server #2301

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
93131d8
added types for create-onboarding-extension-request feature
pankajjs Dec 17, 2024
2de015e
added tests for create-onboarding-extension-request feature
pankajjs Dec 17, 2024
74cd7a0
fix: added request type to support onboarding extension request
pankajjs Dec 17, 2024
2176e1c
feat: added middlwares for create-onboarding-extension-request feature
pankajjs Dec 17, 2024
adaea25
feat: added controller for handling create-onboarding-extension-reque…
pankajjs Dec 17, 2024
2c2f5fa
fix: added middleware and controller for post route to support create…
pankajjs Dec 17, 2024
182bfbf
refactor: refactor 31 days range constant
pankajjs Dec 17, 2024
a142678
fix: fix field name for onboarding extension request types
pankajjs Dec 17, 2024
489a79b
fix: added validation rule and messages for request body
pankajjs Dec 17, 2024
636ba45
fix: fix test name and added tests for request body validation
pankajjs Dec 17, 2024
018ce3e
fix: fix response type, old and new deadline calculation
pankajjs Dec 17, 2024
a21ddc9
fix: fix actual value for validator test as it was failing
pankajjs Dec 17, 2024
c5b4617
fix: replace username with userId
pankajjs Dec 17, 2024
bf7f7a9
fix: handled edge case and change success message
pankajjs Dec 17, 2024
e903c48
test: fix actual message as test was failing
pankajjs Dec 17, 2024
b7dfc5a
refactor: moved onboarding request tests to separate file
pankajjs Dec 17, 2024
4dfdef4
fix: fix lint issue
pankajjs Dec 17, 2024
0eb4954
fix: fix test name and added test for super user case
pankajjs Dec 17, 2024
efba90c
fix: fix failing test and added test for edge case
pankajjs Dec 19, 2024
201071a
fix: added missing field in user type
pankajjs Dec 19, 2024
35b1f44
fix: refactor and handled case when super user request for non-onbard…
pankajjs Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions constants/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const REQUEST_TYPE = {
EXTENSION: "EXTENSION",
TASK: "TASK",
ALL: "ALL",
ONBOARDING: "ONBOARDING",
};

export const REQUEST_LOG_TYPE = {
Expand Down
111 changes: 111 additions & 0 deletions controllers/onboardingExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
ERROR_WHILE_CREATING_REQUEST,
LOG_ACTION,
REQUEST_ALREADY_PENDING,
REQUEST_LOG_TYPE,
REQUEST_STATE,
REQUEST_TYPE,
} from "../constants/requests";
import { userState } from "../constants/userStatus";
import { addLog } from "../models/logs";
import { createRequest, getRequestByKeyValues } from "../models/requests";
import { fetchUser } from "../models/users";
import { getUserStatus } from "../models/userStatus";
import { User } from "../typeDefinitions/users";
import { OnboardingExtension, OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../types/onboardingExtension";

export const createOnboardingExtensionRequestController = async (req: OnboardingExtensionCreateRequest, res: OnboardingExtensionResponse) => {
try {

const data = req.body;
const {user, userExists} = await fetchUser({discordId: data.userId});

if(!userExists) {
return res.boom.notFound("User not found");
}

const { id: userId, discordId: userDiscordId, discordJoinedAt, username} = user as User;
const { data: userStatus } = await getUserStatus(userId);

if(!userStatus || userStatus.currentStatus.state != userState.ONBOARDING){
return res.boom.badRequest("User does not have onboarding status");
}

const requestedUserResponse = await fetchUser({discordId: data.requestedBy});

if(!requestedUserResponse.userExists) {
return res.boom.notFound("User not found");
}

const {roles, discordId: requestedUserDiscordId} = requestedUserResponse.user as User;

if(!(roles?.super_user || (requestedUserDiscordId === userDiscordId))){
return res.boom.unauthorized("Only super user and onboarding user are authorized to create an onboarding extension request");
}

const latestExtensionRequest: OnboardingExtension = await getRequestByKeyValues({
userId: userId,
type: REQUEST_TYPE.ONBOARDING
});

if(latestExtensionRequest && latestExtensionRequest.state === REQUEST_STATE.PENDING){
return res.boom.badRequest(REQUEST_ALREADY_PENDING);
}

const thirtyOneDaysInMillisecond = 31*24*60*60*1000;
const discordJoinedDateInMillisecond = new Date(discordJoinedAt).getTime();
const numberOfDaysInMillisecond = Math.floor(data.numberOfDays)*24*60*60*1000;

let requestNumber: number;
let oldEndsOn: number;
let newEndsOn: number;

if(!latestExtensionRequest){
requestNumber = 1;
oldEndsOn = discordJoinedDateInMillisecond + thirtyOneDaysInMillisecond;
}else if(latestExtensionRequest.state === REQUEST_STATE.REJECTED) {
requestNumber = latestExtensionRequest.requestNumber + 1;
oldEndsOn = latestExtensionRequest.oldEndsOn;
}else{
requestNumber = latestExtensionRequest.requestNumber + 1;
oldEndsOn = latestExtensionRequest.newEndsOn;
}

newEndsOn = oldEndsOn + numberOfDaysInMillisecond;

const onboardingExtension = await createRequest({
type: REQUEST_TYPE.ONBOARDING,
state: REQUEST_STATE.PENDING,
userId: userId,
requestedBy: username,
oldEndsOn: oldEndsOn,
newEndsOn: newEndsOn,
reason: data.reason,
requestNumber: requestNumber,
});

const onboardingExtensionLog = {
type: REQUEST_LOG_TYPE.REQUEST_CREATED,
meta: {
requestId: onboardingExtension.id,
action: LOG_ACTION.CREATE,
userId: userId,
createdAt: Date.now(),
},
body: onboardingExtension,
};

await addLog(onboardingExtensionLog.type, onboardingExtensionLog.meta, onboardingExtensionLog.body);

return res.status(201).json({
message: "Onboarding extension request created successfully!",
data: {
id: onboardingExtension.id,
...onboardingExtension,
}
});
}catch (err) {
logger.error(ERROR_WHILE_CREATING_REQUEST, err);
return res.boom.badImplementation(ERROR_WHILE_CREATING_REQUEST);
}
};
6 changes: 5 additions & 1 deletion controllers/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extens
import { UpdateRequest } from "../types/requests";
import { TaskRequestRequest } from "../types/taskRequests";
import { createTaskRequestController } from "./taskRequestsv2";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../types/onboardingExtension";
import { createOnboardingExtensionRequestController } from "./onboardingExtension";

export const createRequestController = async (
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest,
req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
res: CustomResponse
) => {
const type = req.body.type;
Expand All @@ -26,6 +28,8 @@ export const createRequestController = async (
return await createTaskExtensionRequest(req as ExtensionRequestRequest, res as ExtensionRequestResponse);
case REQUEST_TYPE.TASK:
return await createTaskRequestController(req as TaskRequestRequest, res as CustomResponse);
case REQUEST_TYPE.ONBOARDING:
return await createOnboardingExtensionRequestController(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse);
default:
return res.boom.badRequest("Invalid request type");
}
Expand Down
20 changes: 20 additions & 0 deletions middlewares/skipAuthenticateForOnboardingExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextFunction, Request, Response } from "express"
import { REQUEST_TYPE } from "../constants/requests";

export const skipAuthenticateForOnboardingExtensionRequest = (authenticate, verifyDiscordBot) => {
return async (req: Request, res: Response, next: NextFunction) => {
const type = req.body.type;
const dev = req.query.dev;

if(type === REQUEST_TYPE.ONBOARDING){
if (dev != "true"){
return res.status(501).json({
message: "Feature not implemented"
})
}
return await verifyDiscordBot(req, res, next);
}

return await authenticate(req, res, next)
}
}
42 changes: 42 additions & 0 deletions middlewares/validators/onboardingExtensionRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import joi from "joi";
import { NextFunction } from "express";
import { REQUEST_TYPE } from "../../constants/requests";
import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse } from "../../types/onboardingExtension";

export const createOnboardingExtensionRequestValidator = async (
req: OnboardingExtensionCreateRequest,
_res: OnboardingExtensionResponse,
_next: NextFunction
) => {

const schema = joi
.object()
.strict()
.keys({
numberOfDays: joi.number().required().positive().integer().min(1).messages({
"number.base": "numberOfDays must be a number",
"any.required": "numberOfDays is required",
"number.positive": "numberOfDays must be positive",
"number.min": "numberOfDays must be greater than zero",
"number.integer": "numberOfDays must be a integer"
}),
reason: joi.string().required().messages({
"string.empty": "reason cannot be empty",
"any.required": "reason is required",
}),
type: joi.string().valid(REQUEST_TYPE.ONBOARDING).required().messages({
"string.empty": "type cannot be empty",
"any.required": "type is required",
}),
requestedBy: joi.string().required().messages({
"string.empty": "requestedBy cannot be empty",
"any.required": "requestedBy is required",
}),
userId: joi.string().required().messages({
"string.empty": "userId cannot be empty",
"any.required": "userId is required"
})
});

await schema.validateAsync(req.body, { abortEarly: false });
};
9 changes: 7 additions & 2 deletions middlewares/validators/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ 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";

export const createRequestsMiddleware = async (
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest,
req: OooRequestCreateRequest|ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest,
res: CustomResponse,
next: NextFunction
) => {
Expand All @@ -28,6 +30,9 @@ export const createRequestsMiddleware = async (
case REQUEST_TYPE.TASK:
await createTaskRequestValidator(req as TaskRequestRequest, res as TaskRequestResponse, next);
break;
case REQUEST_TYPE.ONBOARDING:
await createOnboardingExtensionRequestValidator(req as OnboardingExtensionCreateRequest, res as OnboardingExtensionResponse, next);
break;
default:
res.boom.badRequest(`Invalid request type: ${type}`);
}
Expand All @@ -36,7 +41,7 @@ export const createRequestsMiddleware = async (
} catch (error) {
const errorMessages = error.details.map((detail:any) => detail.message);
logger.error(`Error while validating request payload : ${errorMessages}`);
res.boom.badRequest(errorMessages);
return res.boom.badRequest(errorMessages);
}
};

Expand Down
4 changes: 3 additions & 1 deletion routes/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ 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 { skipAuthenticateForOnboardingExtensionRequest } from "../middlewares/skipAuthenticateForOnboardingExtension";
import { verifyDiscordBot } from "../middlewares/authorizeBot";

router.get("/", getRequestsMiddleware, getRequestsController);
router.post("/",authenticate, createRequestsMiddleware, createRequestController);
router.post("/", skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot), createRequestsMiddleware, createRequestController);
Dismissed Show dismissed Hide dismissed
router.put("/:id",authenticate, authorizeRoles([SUPERUSER]), updateRequestsMiddleware, updateRequestController);
module.exports = router;
Loading
Loading