diff --git a/constants/requests.ts b/constants/requests.ts index b1d2836ed..f0fdb9907 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -39,3 +39,17 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request"; export const REQUEST_DOES_NOT_EXIST = "Request does not exist"; export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection"; + +export const TASK_REQUEST_MESSAGES = { + NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request", + USER_NOT_FOUND: "User not found", + TASK_NOT_EXIST: "Task does not exist", + INVALID_EXTERNAL_ISSUE_URL: "External issue url is not valid", + ISSUE_NOT_EXIST: "Issue does not exist", + TASK_REQUEST_EXISTS: "Task request already exists", + TASK_EXISTS_FOR_GIVEN_ISSUE: "Task exists for the given issue.", + TASK_ALREADY_REQUESTED: "Task was already requested", + TASK_REQUEST_CREATED_SUCCESS: "Task request created successfully", + ERROR_CREATING_TASK_REQUEST: "Error while creating task request", + TASK_REQUEST_UPDATED_SUCCESS: "Task request updated successfully", +}; diff --git a/controllers/requests.ts b/controllers/requests.ts index 6e1d55d82..64a97bcee 100644 --- a/controllers/requests.ts +++ b/controllers/requests.ts @@ -11,9 +11,11 @@ import { CustomResponse } from "../typeDefinitions/global"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests"; import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extensionRequestsv2"; import { UpdateRequest } from "../types/requests"; +import { TaskRequestRequest } from "../types/taskRequests"; +import { createTaskRequestController } from "./taskRequestsv2"; export const createRequestController = async ( - req: OooRequestCreateRequest | ExtensionRequestRequest, + req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest, res: CustomResponse ) => { const type = req.body.type; @@ -22,6 +24,8 @@ export const createRequestController = async ( return await createOooRequestController(req as OooRequestCreateRequest, res as OooRequestResponse); case REQUEST_TYPE.EXTENSION: return await createTaskExtensionRequest(req as ExtensionRequestRequest, res as ExtensionRequestResponse); + case REQUEST_TYPE.TASK: + return await createTaskRequestController(req as TaskRequestRequest, res as CustomResponse); default: return res.boom.badRequest("Invalid request type"); } diff --git a/controllers/taskRequestsv2.ts b/controllers/taskRequestsv2.ts new file mode 100644 index 000000000..545634d35 --- /dev/null +++ b/controllers/taskRequestsv2.ts @@ -0,0 +1,174 @@ +import { REQUEST_STATE, TASK_REQUEST_MESSAGES } from "../constants/requests"; +import { TASK_REQUEST_TYPE } from "../constants/taskRequests"; +import { addLog } from "../models/logs"; +import { createRequest, getRequestByKeyValues } from "../models/requests"; +import { fetchTask } from "../models/tasks"; +import { fetchUser } from "../models/users"; +import { fetchIssuesById } from "../services/githubService"; +import { CustomResponse } from "../typeDefinitions/global"; +import { userData } from "../types/global"; +import { TaskRequestRequest } from "../types/taskRequests"; + +export const createTaskRequestController = async (req: TaskRequestRequest, res: CustomResponse) => { + const taskRequestData = req.body; + const requestedBy = req?.userData?.id; + + if (!requestedBy) { + return res.boom.unauthorized(); + } + + if (req.userData.id !== taskRequestData.userId && !req.userData.roles?.super_user) { + return res.boom.forbidden(TASK_REQUEST_MESSAGES.NOT_AUTHORIZED_TO_CREATE_REQUEST); + } + + const userPromise: any = await fetchUser({ userId: taskRequestData.userId }); + const userData: userData = userPromise.user; + if (!userData.id || !userData.username) { + return res.boom.notFound(TASK_REQUEST_MESSAGES.USER_NOT_FOUND); + } + try { + switch (taskRequestData.requestType) { + case TASK_REQUEST_TYPE.ASSIGNMENT: { + if (!req.userData.roles?.super_user) { + return res.boom.unauthorized(TASK_REQUEST_MESSAGES.NOT_AUTHORIZED_TO_CREATE_REQUEST); + } + const { taskData } = await fetchTask(taskRequestData.taskId); + if (!taskData) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.TASK_NOT_EXIST); + } + taskRequestData.taskTitle = taskData?.title; + break; + } + case TASK_REQUEST_TYPE.CREATION: { + let issueData: any; + try { + const url = new URL(taskRequestData.externalIssueUrl); + const issueUrlPaths = url.pathname.split("/"); + const repositoryName = issueUrlPaths[3]; + const issueNumber = issueUrlPaths[5]; + issueData = await fetchIssuesById(repositoryName, issueNumber); + } catch (error) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.INVALID_EXTERNAL_ISSUE_URL); + } + if (!issueData) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.ISSUE_NOT_EXIST); + } + taskRequestData.taskTitle = issueData?.title; + break; + } + } + const existingRequest = await getRequestByKeyValues({ + externalIssueUrl: taskRequestData.externalIssueUrl, + requestType: taskRequestData.requestType, + }); + + if ( + existingRequest && + existingRequest.state === REQUEST_STATE.PENDING && + existingRequest.requestors.includes(requestedBy) + ) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.TASK_REQUEST_EXISTS); + } else if ( + existingRequest && + existingRequest.state === REQUEST_STATE.PENDING && + !existingRequest.requestors.includes(requestedBy) + ) { + existingRequest.requestors.push(requestedBy); + existingRequest.users.push({ + userId: userData.id, + username: userData.username, + proposedStartDate: taskRequestData.proposedStartDate, + proposedDeadline: taskRequestData.proposedDeadline, + description: taskRequestData.description, + markdownEnabled: taskRequestData.markdownEnabled, + firstName: userData.first_name, + lastName: userData.last_name, + state: REQUEST_STATE.PENDING, + requestedAt: Date.now(), + }); + const updatedRequest = await createRequest(existingRequest); + const taskRequestLog = { + type: "taskRequests", + meta: { + taskRequestId: updatedRequest.id, + action: "update", + createdBy: req.userData.id, + createdAt: Date.now(), + lastModifiedBy: req.userData.id, + lastModifiedAt: Date.now(), + }, + body: updatedRequest, + }; + await addLog(taskRequestLog.type, taskRequestLog.meta, taskRequestLog.body); + const data = { + message: TASK_REQUEST_MESSAGES.TASK_REQUEST_UPDATED_SUCCESS, + data: { + id: updatedRequest.id, + ...updatedRequest, + }, + }; + return res.status(200).json(data); + } + + taskRequestData.requestedBy = requestedBy; + const createtaskRequestData = { + externalIssueUrl: taskRequestData.externalIssueUrl, + externalIssueHtmlUrl: taskRequestData.externalIssueHtmlUrl, + requestType: taskRequestData.requestType, + type: taskRequestData.type, + state: REQUEST_STATE.PENDING, + requestedBy: requestedBy, + taskTitle: taskRequestData.taskTitle, + users: [ + { + userId: userData.id, + username: userData.username, + proposedStartDate: taskRequestData.proposedStartDate, + proposedDeadline: taskRequestData.proposedDeadline, + description: taskRequestData.description, + markdownEnabled: taskRequestData.markdownEnabled, + firstName: userData.first_name, + lastName: userData.last_name, + state: REQUEST_STATE.PENDING, + requestedAt: Date.now(), + }, + ], + + requestors: [requestedBy], + }; + const newTaskRequest = await createRequest(createtaskRequestData); + + if (newTaskRequest.isCreationRequestApproved) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.TASK_EXISTS_FOR_GIVEN_ISSUE); + } + if (newTaskRequest.alreadyRequesting) { + return res.boom.badRequest(TASK_REQUEST_MESSAGES.TASK_ALREADY_REQUESTED); + } + + const taskRequestLog = { + type: "taskRequests", + meta: { + taskRequestId: newTaskRequest.id, + action: "create", + createdBy: req.userData.id, + createdAt: Date.now(), + lastModifiedBy: req.userData.id, + lastModifiedAt: Date.now(), + }, + body: newTaskRequest, + }; + await addLog(taskRequestLog.type, taskRequestLog.meta, taskRequestLog.body); + + const data = { + message: TASK_REQUEST_MESSAGES.TASK_REQUEST_CREATED_SUCCESS, + data: { + id: newTaskRequest.id, + ...newTaskRequest, + }, + }; + return res.status(201).json(data); + } catch (err) { + logger.error(`${TASK_REQUEST_MESSAGES.ERROR_CREATING_TASK_REQUEST} : ${err}`); + return res.boom.serverUnavailable(TASK_REQUEST_MESSAGES.ERROR_CREATING_TASK_REQUEST); + } +}; diff --git a/test/integration/requests.test.ts b/test/integration/requests.test.ts index bde701100..bdc2f2867 100644 --- a/test/integration/requests.test.ts +++ b/test/integration/requests.test.ts @@ -28,6 +28,7 @@ import { REQUEST_ALREADY_REJECTED, } from "../../constants/requests"; import { updateTask } from "../../models/tasks"; +import { validTaskAssignmentRequest, validTaskCreqtionRequest } from "../fixtures/taskRequests/taskRequests"; const userData = userDataFixture(); chai.use(chaiHttp); @@ -759,3 +760,64 @@ describe("/requests Extension", function () { }); }); + + +describe("/requests Task", function () { + let userId1: string; + let userJwtToken1: string; + + beforeEach(async function () { + userId1 = await addUser(userData[16]); + userJwtToken1 = authService.generateAuthToken({ userId: userId1 }); + }); + + afterEach(async function () { + await cleanDb(); + }); + + describe("POST /requests", function () { + it("should return 401(Unauthorized) if user is not logged in", function (done) { + chai + .request(app) + .post("/requests?dev=true") + .send(validTaskCreqtionRequest) + .end(function (err, res) { + expect(res).to.have.status(401); + done(); + }); + }); + + it("should not create a new task request if issue does not exist", function (done) { + let taskRequestObj = validTaskCreqtionRequest + taskRequestObj.externalIssueUrl = "https://api.github.com/repos/Real-Dev-Squad/website-my/issues/1245"; + taskRequestObj.userId = userId1; + chai + .request(app) + .post("/requests?dev=true") + .set("cookie", `${cookieName}=${userJwtToken1}`) + .send(taskRequestObj) + .end(function (err, res) { + expect(res).to.have.status(400); + expect(res.body).to.have.property("message"); + expect(res.body.message).to.equal("Issue does not exist"); + done(); + }); + }); + + it("should not create a new task request if task id is not present in the request body", function (done) { + let taskRequestObj = validTaskAssignmentRequest + delete taskRequestObj.taskId; + chai + .request(app) + .post("/requests?dev=true") + .set("cookie", `${cookieName}=${userJwtToken1}`) + .send(taskRequestObj) + .end(function (err, res) { + expect(res).to.have.status(400); + expect(res.body).to.have.property("message"); + expect(res.body.message).to.equal('taskId is required when requestType is ASSIGNMENT'); + done(); + }); + }); + }); +});