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 #2307

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
4 changes: 4 additions & 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 Expand Up @@ -53,3 +54,6 @@ export const TASK_REQUEST_MESSAGES = {
ERROR_CREATING_TASK_REQUEST: "Error while creating task request",
TASK_REQUEST_UPDATED_SUCCESS: "Task request updated successfully",
};

export const ONBOARDING_REQUEST_CREATED_SUCCESSFULLY = "Onboarding extension request created successfully"
export const UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST = "Only super user and onboarding user are authorized to create an onboarding extension request"
107 changes: 107 additions & 0 deletions controllers/onboardingExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
ERROR_WHILE_CREATING_REQUEST,
LOG_ACTION,
ONBOARDING_REQUEST_CREATED_SUCCESSFULLY,
REQUEST_ALREADY_PENDING,
REQUEST_LOG_TYPE,
REQUEST_STATE,
REQUEST_TYPE,
UNAUTHORIZED_TO_CREATE_ONBOARDING_EXTENSION_REQUEST,
} 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, discordJoinedAt, username} = user as User;
const { data: userStatus } = await getUserStatus(userId);

if(!userStatus || userStatus.currentStatus.state != userState.ONBOARDING){
return res.boom.unauthorized(UNAUTHORIZED_TO_CREATE_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;
const currentDate = Date.now();

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;
}

if(currentDate > oldEndsOn){
newEndsOn = currentDate + numberOfDaysInMillisecond;
}
else {
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_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)
}
}
38 changes: 38 additions & 0 deletions middlewares/validators/onboardingExtensionRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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",
}),
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);
router.put("/:id",authenticate, authorizeRoles([SUPERUSER]), updateRequestsMiddleware, updateRequestController);
module.exports = router;
6 changes: 4 additions & 2 deletions typeDefinitions/users.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type User = {
id?: string
username?: string;
first_name?: string;
last_name?: string;
Expand All @@ -17,7 +18,8 @@ export type User = {
roles?: {
member?: boolean;
in_discord?: boolean;
};
super_user?: boolean;
}
tokens?: {
githubAccessToken?: string;
};
Expand All @@ -29,4 +31,4 @@ export type User = {
};
incompleteUserDetails?: boolean;
nickname_synced?: boolean;
};
};
39 changes: 39 additions & 0 deletions types/onboardingExtension.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Request, Response } from "express";
import { Boom } from "express-boom";
import { REQUEST_STATE, REQUEST_TYPE } from "../constants/requests";
import { RequestQuery } from "./requests";

export type OnboardingExtension = {
id: string;
type: REQUEST_TYPE.ONBOARDING;
oldEndsOn: number;
newEndsOn: number;
message?: string;
reason: string;
requestedBy: string;
state: REQUEST_STATE;
lastModifiedBy?: string;
createdAt: Timestamp;
updatedAt: Timestamp;
requestNumber: number;
userId: string;
}

export type CreateOnboardingExtensionBody = {
type: string;
numberOfDays: number;
userId: string;
reason: string;
}

export type OnboardingExtensionRequestQuery = RequestQuery & {
dev?: string
}

export type OnboardingExtensionResponse = Response & {
boom: Boom
}
export type OnboardingExtensionCreateRequest = Request & {
body: CreateOnboardingExtensionBody;
query: OnboardingExtensionRequestQuery;
}
Loading