diff --git a/src/pages/api/approval-workflows/[id]/edit.ts b/src/pages/api/approval-workflows/[id]/edit.ts index a547f0cf..e675addb 100644 --- a/src/pages/api/approval-workflows/[id]/edit.ts +++ b/src/pages/api/approval-workflows/[id]/edit.ts @@ -17,7 +17,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } diff --git a/src/pages/api/approval-workflows/[id]/index.ts b/src/pages/api/approval-workflows/[id]/index.ts index c50026c0..d6832587 100644 --- a/src/pages/api/approval-workflows/[id]/index.ts +++ b/src/pages/api/approval-workflows/[id]/index.ts @@ -3,6 +3,8 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import { sessionOptions } from "@/lib/session"; import { requestUser } from "@/utils/api"; import { deleteApprovalWorkflow, getApprovalWorkflow, updateApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { getEntityWithRoles } from "@/utils/entities.be"; +import { doesEntityAllow } from "@/utils/permissions"; import { withIronSessionApiRoute } from "iron-session/next"; import { ObjectId } from "mongodb"; import type { NextApiRequest, NextApiResponse } from "next"; @@ -19,20 +21,30 @@ async function del(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } - const { id } = req.query as { id?: string }; + const { id } = req.query as { id: string }; + const workflow = await getApprovalWorkflow("active-workflows", id); - if (id) return res.status(200).json(await deleteApprovalWorkflow("active-workflows", id)); + if (!workflow) return res.status(404).json({ ok: false }); + + const entity = await getEntityWithRoles(workflow.entityId); + if (!entity) return res.status(404).json({ ok: false }); + + if (!doesEntityAllow(user, entity, "delete_workflow") && !["admin", "developer"].includes(user.type)) { + return res.status(403).json({ ok: false }); + } + + return res.status(200).json(await deleteApprovalWorkflow("active-workflows", id)); } async function put(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } @@ -50,7 +62,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } diff --git a/src/pages/api/approval-workflows/create.ts b/src/pages/api/approval-workflows/create.ts index 96889899..aa88f9348 100644 --- a/src/pages/api/approval-workflows/create.ts +++ b/src/pages/api/approval-workflows/create.ts @@ -22,7 +22,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } diff --git a/src/pages/api/approval-workflows/index.ts b/src/pages/api/approval-workflows/index.ts index 26636186..874f0ab0 100644 --- a/src/pages/api/approval-workflows/index.ts +++ b/src/pages/api/approval-workflows/index.ts @@ -25,7 +25,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } @@ -36,7 +36,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res); if (!user) return res.status(401).json({ ok: false }); - if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) { + if (!["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) { return res.status(403).json({ ok: false }); } diff --git a/src/pages/approval-workflows/[id]/edit.tsx b/src/pages/approval-workflows/[id]/edit.tsx index 7a026bff..aec4fd1f 100644 --- a/src/pages/approval-workflows/[id]/edit.tsx +++ b/src/pages/approval-workflows/[id]/edit.tsx @@ -6,10 +6,12 @@ import Layout from "@/components/High/Layout"; import { ApprovalWorkflow, EditableApprovalWorkflow, EditableWorkflowStep, getUserTypeLabelShort } from "@/interfaces/approval.workflow"; import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; -import { redirect, serialize } from "@/utils"; +import { findBy, redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { getApprovalWorkflow } from "@/utils/approval.workflows.be"; +import { getEntityWithRoles } from "@/utils/entities.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { doesEntityAllow } from "@/utils/permissions"; import { getEntityUsers } from "@/utils/users.be"; import axios from "axios"; import { LayoutGroup, motion } from "framer-motion"; @@ -30,9 +32,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params } const { id } = params as { id: string }; const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id); + if (!workflow) return redirect("/approval-workflows"); - if (!workflow) - return redirect("/approval-workflows") + const entityWithRole = await getEntityWithRoles(workflow.entityId); + if (!entityWithRole) return redirect("/approval-workflows"); + + if (!doesEntityAllow(user, entityWithRole, "edit_workflow")) return redirect("/approval-workflows"); return { props: serialize({ diff --git a/src/pages/approval-workflows/create.tsx b/src/pages/approval-workflows/create.tsx index b61d6e24..41a9d7dd 100644 --- a/src/pages/approval-workflows/create.tsx +++ b/src/pages/approval-workflows/create.tsx @@ -1,6 +1,5 @@ import Tip from "@/components/ApprovalWorkflows/Tip"; import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm"; -import Layout from "@/components/High/Layout"; import Button from "@/components/Low/Button"; import Input from "@/components/Low/Input"; import Select from "@/components/Low/Select"; @@ -8,11 +7,13 @@ import { ApprovalWorkflow, EditableApprovalWorkflow } from "@/interfaces/approva import { Entity } from "@/interfaces/entity"; import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; -import { redirect, serialize } from "@/utils"; +import { mapBy, redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { getApprovalWorkflowsByEntities } from "@/utils/approval.workflows.be"; -import { getEntities } from "@/utils/entities.be"; +import { getEntitiesWithRoles } from "@/utils/entities.be"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { findAllowedEntities } from "@/utils/permissions"; +import { isAdmin } from "@/utils/users"; import { getEntitiesUsers } from "@/utils/users.be"; import axios from "axios"; import { AnimatePresence, LayoutGroup, motion } from "framer-motion"; @@ -31,10 +32,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { const user = await requestUser(req, res) if (!user) return redirect("/login") - if (shouldRedirectHome(user) || !["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) + if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/") - const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id)); + const entityIDS = mapBy(user.entities, "id"); + const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS); + const userEntitiesWithLabel = findAllowedEntities(user, entities, "configure_workflows"); const allConfiguredWorkflows = await getApprovalWorkflowsByEntities("configured-workflows", userEntitiesWithLabel.map(entity => entity.id)); diff --git a/src/pages/approval-workflows/index.tsx b/src/pages/approval-workflows/index.tsx index 84b65c1c..a33d2967 100644 --- a/src/pages/approval-workflows/index.tsx +++ b/src/pages/approval-workflows/index.tsx @@ -4,57 +4,34 @@ import Button from "@/components/Low/Button"; import Input from "@/components/Low/Input"; import Select from "@/components/Low/Select"; import useApprovalWorkflows from "@/hooks/useApprovalWorkflows"; -import {useAllowedEntities, useAllowedEntitiesSomePermissions, useEntityPermission} from "@/hooks/useEntityPermissions"; -import {Module, ModuleTypeLabels} from "@/interfaces"; -import {ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel} from "@/interfaces/approval.workflow"; -import {Entity, EntityWithRoles} from "@/interfaces/entity"; -import {User} from "@/interfaces/user"; -import {sessionOptions} from "@/lib/session"; -import {redirect, serialize} from "@/utils"; -import {requestUser} from "@/utils/api"; -import {getApprovalWorkflows} from "@/utils/approval.workflows.be"; -import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; -import {findAllowedEntities} from "@/utils/permissions"; -import {getSpecificUsers} from "@/utils/users.be"; -import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; +import { useAllowedEntities, useAllowedEntitiesSomePermissions, useEntityPermission } from "@/hooks/useEntityPermissions"; +import { Module, ModuleTypeLabels } from "@/interfaces"; +import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow"; +import { Entity, EntityWithRoles } from "@/interfaces/entity"; +import { User } from "@/interfaces/user"; +import { sessionOptions } from "@/lib/session"; +import { mapBy, redirect, serialize } from "@/utils"; +import { requestUser } from "@/utils/api"; +import { getApprovalWorkflows } from "@/utils/approval.workflows.be"; +import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { doesEntityAllow, findAllowedEntities } from "@/utils/permissions"; +import { isAdmin } from "@/utils/users"; +import { getSpecificUsers } from "@/utils/users.be"; +import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import axios from "axios"; import clsx from "clsx"; -import {withIronSessionSsr} from "iron-session/next"; +import { withIronSessionSsr } from "iron-session/next"; import Head from "next/head"; import Link from "next/link"; -import {useEffect, useState} from "react"; -import {BsTrash} from "react-icons/bs"; -import {FaRegEdit} from "react-icons/fa"; -import {IoIosAddCircleOutline} from "react-icons/io"; -import {toast, ToastContainer} from "react-toastify"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { BsTrash } from "react-icons/bs"; +import { FaRegEdit } from "react-icons/fa"; +import { IoIosAddCircleOutline } from "react-icons/io"; +import { toast, ToastContainer } from "react-toastify"; -const columnHelper = createColumnHelper(); - -export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { - const user = await requestUser(req, res); - if (!user) return redirect("/login"); - - if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/"); - - const workflows = await getApprovalWorkflows("active-workflows"); - - const allAssigneeIds: string[] = [...new Set(workflows.map((workflow) => workflow.steps.map((step) => step.assignees).flat()).flat())]; - - const entities = await getEntitiesWithRoles(user.entities.map((entity) => entity.id)); - const allowedEntities = findAllowedEntities(user, entities, "view_workflows"); - - return { - props: serialize({ - user, - initialWorkflows: workflows, - workflowsAssignees: await getSpecificUsers(allAssigneeIds), - userEntitiesWithLabel: entities, - }), - }; -}, sessionOptions); - -const StatusClassNames: {[key in ApprovalWorkflowStatus]: string} = { +const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = { approved: "bg-green-100 text-green-800 border border-green-300 before:content-[''] before:w-2 before:h-2 before:bg-green-500 before:rounded-full before:inline-block before:mr-2", pending: @@ -84,6 +61,40 @@ const STATUS_OPTIONS = [ }, ]; +const columnHelper = createColumnHelper(); + +export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { + const user = await requestUser(req, res); + if (!user) return redirect("/login"); + + if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/"); + + const workflows = await getApprovalWorkflows("active-workflows"); + + const allAssigneeIds: string[] = [ + ...new Set( + workflows + .map(workflow => workflow.steps + .map(step => step.assignees) + .flat() + ).flat() + ) + ]; + + const entityIDS = mapBy(user.entities, "id"); + const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS); + const allowedEntities = findAllowedEntities(user, entities, "view_workflows"); + + return { + props: serialize({ + user, + initialWorkflows: workflows, + workflowsAssignees: await getSpecificUsers(allAssigneeIds), + userEntitiesWithLabel: allowedEntities, + }), + }; +}, sessionOptions); + interface Props { user: User; initialWorkflows: ApprovalWorkflow[]; @@ -91,8 +102,8 @@ interface Props { userEntitiesWithLabel: EntityWithRoles[]; } -export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel}: Props) { - const {workflows, reload} = useApprovalWorkflows(); +export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel }: Props) { + const { workflows, reload } = useApprovalWorkflows(); const currentWorkflows = workflows || initialWorkflows; const [filteredWorkflows, setFilteredWorkflows] = useState([]); @@ -101,8 +112,10 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi const [entityFilter, setEntityFilter] = useState(undefined); const [nameFilter, setNameFilter] = useState(""); - const allowedEntities = useAllowedEntities(user, userEntitiesWithLabel, "view_workflows"); - const allowedSomeEntities = useAllowedEntitiesSomePermissions(user, userEntitiesWithLabel, ["view_workflows", "create_workflow"]); + const router = useRouter(); + + /* const allowedEntities = useAllowedEntities(user, userEntitiesWithLabel, "view_workflows"); + const allowedSomeEntities = useAllowedEntitiesSomePermissions(user, userEntitiesWithLabel, ["view_workflows", "create_workflow"]); */ const ENTITY_OPTIONS = [ ...userEntitiesWithLabel @@ -157,10 +170,8 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi reload(); }) .catch((reason) => { - if (reason.response.status === 404) { - toast.error("Approval Workflow not found!"); - } else if (reason.response.status === 403) { - toast.error("You do not have permission to delete an Approval Workflow!"); + if (reason.response.status === 403) { + toast.error("You do not have permission to delete this Approval Workflow!"); } else { toast.error("Something went wrong, please try again later."); } @@ -249,7 +260,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi columnHelper.accessor("steps", { header: "ACTIONS", id: "actions", - cell: ({row}) => { + cell: ({ row }) => { const steps = row.original.steps; const currentStep = steps.find((step) => !step.completed); const rejected = steps.find((step) => step.rejected); @@ -259,6 +270,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi {currentStep && !rejected && ( - e.stopPropagation()} + )} ); @@ -340,7 +355,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
- +
{table.getHeaderGroups().map((headerGroup) => ( @@ -357,7 +372,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi (window.location.href = `/approval-workflows/${row.original._id?.toString()}`)} - style={{cursor: "pointer"}} + style={{ cursor: "pointer" }} className="bg-purple-50"> {row.getVisibleCells().map((cell, cellIndex) => { const lastCellIndex = row.getVisibleCells().length - 1; diff --git a/src/pages/entities/[id]/roles/[role].tsx b/src/pages/entities/[id]/roles/[role].tsx index 2c90f9b9..2d344e18 100644 --- a/src/pages/entities/[id]/roles/[role].tsx +++ b/src/pages/entities/[id]/roles/[role].tsx @@ -109,7 +109,7 @@ const ASSIGNMENT_MANAGEMENT: PermissionLayout[] = [ const WORKFLOW_MANAGEMENT: PermissionLayout[] = [ {label: "View Workflows", key: "view_workflows"}, - {label: "Create Workflow", key: "create_workflow"}, + {label: "Configure Workflows", key: "configure_workflows"}, {label: "Edit Workflow", key: "edit_workflow"}, {label: "Delete Workflow", key: "delete_workflow"}, ]; diff --git a/src/resources/entityPermissions.ts b/src/resources/entityPermissions.ts index b46ed0d0..5691967a 100644 --- a/src/resources/entityPermissions.ts +++ b/src/resources/entityPermissions.ts @@ -69,7 +69,7 @@ export type RolePermission = | "view_approval_workflows" | "update_exam_privacy" | "view_workflows" - | "create_workflow" + | "configure_workflows" | "edit_workflow" | "delete_workflow"; @@ -81,7 +81,6 @@ export const DEFAULT_PERMISSIONS: RolePermission[] = [ "view_entity_roles", "view_statistics", "download_statistics_report", - "view_approval_workflows", ]; export const ADMIN_PERMISSIONS: RolePermission[] = [ @@ -153,7 +152,7 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [ "pay_entity", "view_payment_record", "update_exam_privacy", - "create_workflow", + "configure_workflows", "view_workflows", "edit_workflow", "delete_workflow",