From eabfcd026b60a3b8e7eb97ab09fb7cdfce7625c2 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Wed, 11 Dec 2024 14:09:10 +0000 Subject: [PATCH] ENCOA-273 --- src/interfaces/entity.ts | 29 +++++----- src/pages/api/entities/[id]/index.ts | 37 +++++++----- src/pages/api/entities/index.ts | 17 +++--- src/pages/api/hello.ts | 7 +-- src/pages/entities/[id]/index.tsx | 87 +++++++++++++++++++--------- src/pages/entities/create.tsx | 60 ++++++++++--------- src/pages/entities/index.tsx | 4 +- 7 files changed, 144 insertions(+), 97 deletions(-) diff --git a/src/interfaces/entity.ts b/src/interfaces/entity.ts index f543d872..3cb45e74 100644 --- a/src/interfaces/entity.ts +++ b/src/interfaces/entity.ts @@ -1,31 +1,32 @@ import { RolePermission } from "@/resources/entityPermissions"; export interface Entity { - id: string; - label: string; + id: string; + label: string; + licenses: number } export interface Role { - id: string; - entityID: string; - permissions: RolePermission[]; - label: string; - isDefault?: boolean + id: string; + entityID: string; + permissions: RolePermission[]; + label: string; + isDefault?: boolean } export interface EntityWithRoles extends Entity { - roles: Role[]; + roles: Role[]; }; export type WithLabeledEntities = T extends { entities: { id: string; role: string }[] } - ? Omit & { entities: { id: string; label?: string; role: string, roleLabel?: string }[] } - : T; + ? Omit & { entities: { id: string; label?: string; role: string, roleLabel?: string }[] } + : T; export type WithEntity = T extends { entity?: string } - ? Omit & { entity: Entity } - : T; + ? Omit & { entity: Entity } + : T; export type WithEntities = T extends { entities: { id: string; role: string }[] } - ? Omit & { entities: { entity?: Entity; role?: Role }[] } - : T; + ? Omit & { entities: { entity?: Entity; role?: Role }[] } + : T; diff --git a/src/pages/api/entities/[id]/index.ts b/src/pages/api/entities/[id]/index.ts index efe76896..c329f832 100644 --- a/src/pages/api/entities/[id]/index.ts +++ b/src/pages/api/entities/[id]/index.ts @@ -1,13 +1,14 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from "next"; -import {withIronSessionApiRoute} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {deleteEntity, getEntity, getEntityWithRoles} from "@/utils/entities.be"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { deleteEntity, getEntity, getEntityWithRoles } from "@/utils/entities.be"; import client from "@/lib/mongodb"; -import {Entity} from "@/interfaces/entity"; +import { Entity } from "@/interfaces/entity"; import { doesEntityAllow } from "@/utils/permissions"; import { getUser } from "@/utils/users.be"; import { requestUser } from "@/utils/api"; +import { isAdmin } from "@/utils/users"; const db = client.db(process.env.MONGODB_DB); @@ -23,7 +24,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res) if (!user) return res.status(401).json({ ok: false }); - const {id, showRoles} = req.query as {id: string; showRoles: string}; + const { id, showRoles } = req.query as { id: string; showRoles: string }; const entity = await (!!showRoles ? getEntityWithRoles : getEntity)(id); res.status(200).json(entity); @@ -39,23 +40,31 @@ async function del(req: NextApiRequest, res: NextApiResponse) { if (!entity) return res.status(404).json({ ok: false }) if (!doesEntityAllow(user, entity, "delete_entity") && !["admin", "developer"].includes(user.type)) - return res.status(403).json({ok: false}) + return res.status(403).json({ ok: false }) await deleteEntity(entity) - return res.status(200).json({ok: true}); + return res.status(200).json({ ok: true }); } async function patch(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res) - if (!user) return res.status(401).json({ ok: false }); + if (!user) return res.status(401).json({ ok: false }); - const {id} = req.query as {id: string}; + const { id } = req.query as { id: string }; - if (!user.entities.map((x) => x.id).includes(id)) { - return res.status(403).json({ok: false}); + if (!user.entities.map((x) => x.id).includes(id) && !isAdmin(user)) { + return res.status(403).json({ ok: false }); } - const entity = await db.collection("entities").updateOne({id}, {$set: {label: req.body.label}}); + if (req.body.label) { + const entity = await db.collection("entities").updateOne({ id }, { $set: { label: req.body.label } }); + return res.status(200).json({ ok: entity.acknowledged }); + } - return res.status(200).json({ok: entity.acknowledged}); + if (req.body.licenses) { + const entity = await db.collection("entities").updateOne({ id }, { $set: { licenses: req.body.licenses } }); + return res.status(200).json({ ok: entity.acknowledged }); + } + + return res.status(200).json({ ok: true }); } diff --git a/src/pages/api/entities/index.ts b/src/pages/api/entities/index.ts index 7c8b3896..144a77bd 100644 --- a/src/pages/api/entities/index.ts +++ b/src/pages/api/entities/index.ts @@ -1,10 +1,10 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type {NextApiRequest, NextApiResponse} from "next"; -import {withIronSessionApiRoute} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; -import {addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles} from "@/utils/entities.be"; -import {Entity} from "@/interfaces/entity"; -import {v4} from "uuid"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; +import { addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles } from "@/utils/entities.be"; +import { Entity } from "@/interfaces/entity"; +import { v4 } from "uuid"; import { requestUser } from "@/utils/api"; export default withIronSessionApiRoute(handler, sessionOptions); @@ -18,7 +18,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const user = await requestUser(req, res) if (!user) return res.status(401).json({ ok: false }); - const {showRoles} = req.query as {showRoles: string}; + const { showRoles } = req.query as { showRoles: string }; const getFn = showRoles ? getEntitiesWithRoles : getEntities; @@ -31,12 +31,13 @@ async function post(req: NextApiRequest, res: NextApiResponse) { if (!user) return res.status(401).json({ ok: false }); if (!["admin", "developer"].includes(user.type)) { - return res.status(403).json({ok: false}); + return res.status(403).json({ ok: false }); } const entity: Entity = { id: v4(), label: req.body.label, + licenses: req.body.licenses }; const members = req.body.members as string[] | undefined || [] diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts index 6bd87e9d..76280a02 100644 --- a/src/pages/api/hello.ts +++ b/src/pages/api/hello.ts @@ -1,6 +1,6 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import client from "@/lib/mongodb"; -import type {NextApiRequest, NextApiResponse} from "next"; +import type { NextApiRequest, NextApiResponse } from "next"; const db = client.db(process.env.MONGODB_DB); @@ -9,8 +9,5 @@ type Data = { }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - // await db.collection("users").updateMany({}, {$set: {entities: []}}); - await db.collection("invites").deleteMany({}); - - res.status(200).json({name: "John Doe"}); + res.status(200).json({ name: "John Doe" }); } diff --git a/src/pages/entities/[id]/index.tsx b/src/pages/entities/[id]/index.tsx index 320ae609..1546d933 100644 --- a/src/pages/entities/[id]/index.tsx +++ b/src/pages/entities/[id]/index.tsx @@ -4,34 +4,35 @@ import Layout from "@/components/High/Layout"; import Select from "@/components/Low/Select"; import Tooltip from "@/components/Low/Tooltip"; import { useEntityPermission } from "@/hooks/useEntityPermissions"; -import {useListSearch} from "@/hooks/useListSearch"; +import { useListSearch } from "@/hooks/useListSearch"; import usePagination from "@/hooks/usePagination"; -import {Entity, EntityWithRoles, Role} from "@/interfaces/entity"; -import {GroupWithUsers, User} from "@/interfaces/user"; -import {sessionOptions} from "@/lib/session"; -import {USER_TYPE_LABELS} from "@/resources/user"; +import { Entity, EntityWithRoles, Role } from "@/interfaces/entity"; +import { GroupWithUsers, User } from "@/interfaces/user"; +import { sessionOptions } from "@/lib/session"; +import { USER_TYPE_LABELS } from "@/resources/user"; import { findBy, mapBy, redirect, serialize } from "@/utils"; -import {getEntityWithRoles} from "@/utils/entities.be"; -import {convertToUsers, getGroup} from "@/utils/groups.be"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; -import {checkAccess, doesEntityAllow, getTypesOfUser} from "@/utils/permissions"; -import {getUserName, isAdmin} from "@/utils/users"; -import {filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers} from "@/utils/users.be"; +import { getEntityWithRoles } from "@/utils/entities.be"; +import { convertToUsers, getGroup } from "@/utils/groups.be"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { checkAccess, doesEntityAllow, getTypesOfUser } from "@/utils/permissions"; +import { getUserName, isAdmin } from "@/utils/users"; +import { filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers } from "@/utils/users.be"; import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; -import {withIronSessionSsr} from "iron-session/next"; +import { withIronSessionSsr } from "iron-session/next"; import moment from "moment"; import Head from "next/head"; import Link from "next/link"; -import {useRouter} from "next/router"; -import {Divider} from "primereact/divider"; -import {useEffect, useMemo, useState} from "react"; +import { useRouter } from "next/router"; +import { Divider } from "primereact/divider"; +import { useEffect, useMemo, useState } from "react"; import { BsChevronLeft, BsClockFill, BsEnvelopeFill, BsFillPersonVcardFill, + BsHash, BsPerson, BsPlus, BsSquare, @@ -40,15 +41,15 @@ import { BsTrash, BsX, } from "react-icons/bs"; -import {toast} from "react-toastify"; +import { toast } from "react-toastify"; -export const getServerSideProps = withIronSessionSsr(async ({req, params}) => { +export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => { const user = req.session.user as User; if (!user) return redirect("/login") if (shouldRedirectHome(user)) return redirect("/") - const {id} = params as {id: string}; + const { id } = params as { id: string }; const entity = await getEntityWithRoles(id); if (!entity) return redirect("/entities") @@ -56,12 +57,12 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => { if (!doesEntityAllow(user, entity, "view_entities")) return redirect(`/entities`) const linkedUsers = await (isAdmin(user) ? getUsers() : getEntitiesUsers(mapBy(user.entities, 'id'), - {$and: [{type: {$ne: "developer"}}, {type: {$ne: "admin"}}]})) + { $and: [{ type: { $ne: "developer" } }, { type: { $ne: "admin" } }] })) const entityUsers = await (isAdmin(user) ? getEntityUsers(id) : filterAllowedUsers(user, [entity])); const usersWithRole = entityUsers.map((u) => { const e = u.entities.find((e) => e.id === id); - return {...u, role: findBy(entity.roles, 'id', e?.role)}; + return { ...u, role: findBy(entity.roles, 'id', e?.role) }; }); return { @@ -74,7 +75,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => { }; }, sessionOptions); -type UserWithRole = User & {role?: Role}; +type UserWithRole = User & { role?: Role }; interface Props { user: User; @@ -83,7 +84,7 @@ interface Props { linkedUsers: User[]; } -export default function Home({user, entity, users, linkedUsers}: Props) { +export default function Home({ user, entity, users, linkedUsers }: Props) { const [isAdding, setIsAdding] = useState(false); const [isLoading, setIsLoading] = useState(false); const [selectedUsers, setSelectedUsers] = useState([]); @@ -110,7 +111,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) { setIsLoading(true); axios - .patch(`/api/entities/${entity.id}/users`, {add: false, members: selectedUsers}) + .patch(`/api/entities/${entity.id}/users`, { add: false, members: selectedUsers }) .then(() => { toast.success("The entity has been updated successfully!"); router.replace(router.asPath); @@ -132,7 +133,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) { const defaultRole = findBy(entity.roles, 'isDefault', true)! axios - .patch(`/api/entities/${entity.id}/users`, {add: true, members: selectedUsers, role: defaultRole.id}) + .patch(`/api/entities/${entity.id}/users`, { add: true, members: selectedUsers, role: defaultRole.id }) .then(() => { toast.success("The entity has been updated successfully!"); router.replace(router.asPath); @@ -153,7 +154,28 @@ export default function Home({user, entity, users, linkedUsers}: Props) { setIsLoading(true); axios - .patch(`/api/entities/${entity.id}`, {label}) + .patch(`/api/entities/${entity.id}`, { label }) + .then(() => { + toast.success("The entity has been updated successfully!"); + router.replace(router.asPath); + }) + .catch((e) => { + console.error(e); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; + + const editLicenses = () => { + if (!isAdmin(user)) return; + + const licenses = prompt("Update the number of licenses:", (entity.licenses || 0).toString()); + if (!licenses) return; + if (!parseInt(licenses) || parseInt(licenses) <= 0) return toast.error("Write a valid number of licenses!") + + setIsLoading(true); + axios + .patch(`/api/entities/${entity.id}`, { licenses }) .then(() => { toast.success("The entity has been updated successfully!"); router.replace(router.asPath); @@ -190,7 +212,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) { setIsLoading(true); axios - .post(`/api/roles/${role}/users`, {users: selectedUsers}) + .post(`/api/roles/${role}/users`, { users: selectedUsers }) .then(() => { toast.success("The role has been assigned successfully!"); router.replace(router.asPath); @@ -274,7 +296,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) { className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl"> -

{entity.label}

+

{entity.label} {isAdmin(user) && `- ${entity.licenses || 0} licenses`}

@@ -285,6 +307,15 @@ export default function Home({user, entity, users, linkedUsers}: Props) { Rename Entity + {isAdmin(user) && ( + + )} ))} diff --git a/src/pages/entities/create.tsx b/src/pages/entities/create.tsx index f049fa7a..a3f92b54 100644 --- a/src/pages/entities/create.tsx +++ b/src/pages/entities/create.tsx @@ -3,32 +3,32 @@ import Layout from "@/components/High/Layout"; import Input from "@/components/Low/Input"; import Select from "@/components/Low/Select"; import Tooltip from "@/components/Low/Tooltip"; -import {useListSearch} from "@/hooks/useListSearch"; +import { useListSearch } from "@/hooks/useListSearch"; import usePagination from "@/hooks/usePagination"; -import {Entity, EntityWithRoles} from "@/interfaces/entity"; -import {User} from "@/interfaces/user"; -import {sessionOptions} from "@/lib/session"; -import {USER_TYPE_LABELS} from "@/resources/user"; -import {mapBy, redirect, serialize} from "@/utils"; -import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be"; -import {shouldRedirectHome} from "@/utils/navigation.disabled"; -import {getUserName} from "@/utils/users"; -import {getLinkedUsers, getUsers} from "@/utils/users.be"; +import { Entity, EntityWithRoles } from "@/interfaces/entity"; +import { User } from "@/interfaces/user"; +import { sessionOptions } from "@/lib/session"; +import { USER_TYPE_LABELS } from "@/resources/user"; +import { mapBy, redirect, serialize } from "@/utils"; +import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be"; +import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import { getUserName } from "@/utils/users"; +import { getLinkedUsers, getUsers } from "@/utils/users.be"; import axios from "axios"; import clsx from "clsx"; -import {withIronSessionSsr} from "iron-session/next"; +import { withIronSessionSsr } from "iron-session/next"; import moment from "moment"; import Head from "next/head"; import Link from "next/link"; -import {useRouter} from "next/router"; -import {Divider} from "primereact/divider"; -import {useState} from "react"; -import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs"; -import {toast, ToastContainer} from "react-toastify"; +import { useRouter } from "next/router"; +import { Divider } from "primereact/divider"; +import { useState } from "react"; +import { BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill } from "react-icons/bs"; +import { toast, ToastContainer } from "react-toastify"; import { requestUser } from "@/utils/api"; import { findAllowedEntities } from "@/utils/permissions"; -export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { +export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { const user = await requestUser(req, res) if (!user) return redirect("/login") @@ -38,7 +38,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const users = await getUsers() return { - props: serialize({user, users: users.filter((x) => x.id !== user.id)}), + props: serialize({ user, users: users.filter((x) => x.id !== user.id) }), }; }, sessionOptions); @@ -47,13 +47,14 @@ interface Props { users: User[]; } -export default function Home({user, users}: Props) { +export default function Home({ user, users }: Props) { const [isLoading, setIsLoading] = useState(false); const [selectedUsers, setSelectedUsers] = useState([]); const [label, setLabel] = useState(""); + const [licenses, setLicenses] = useState(0); - const {rows, renderSearch} = useListSearch([["name"], ["corporateInformation", "companyInformation", "name"]], users); - const {items, renderMinimal} = usePagination(rows, 16); + const { rows, renderSearch } = useListSearch([["name"], ["corporateInformation", "companyInformation", "name"]], users); + const { items, renderMinimal } = usePagination(rows, 16); const router = useRouter(); @@ -64,7 +65,7 @@ export default function Home({user, users}: Props) { setIsLoading(true); axios - .post(`/api/entities`, {label, members: selectedUsers}) + .post(`/api/entities`, { label, licenses, members: selectedUsers }) .then((result) => { toast.success("Your entity has been created successfully!"); router.replace(`/entities/${result.data.id}`); @@ -104,7 +105,7 @@ export default function Home({user, users}: Props) {
-
- Entity Label: - +
+
+ Entity Label: + +
+ +
+ Licenses: + setLicenses(parseInt(v))} type="number" placeholder="12" /> +
diff --git a/src/pages/entities/index.tsx b/src/pages/entities/index.tsx index 4674ada8..8f588ced 100644 --- a/src/pages/entities/index.tsx +++ b/src/pages/entities/index.tsx @@ -6,7 +6,7 @@ import { ToastContainer } from "react-toastify"; import Layout from "@/components/High/Layout"; import { GroupWithUsers, User } from "@/interfaces/user"; import { shouldRedirectHome } from "@/utils/navigation.disabled"; -import { getUserName } from "@/utils/users"; +import { getUserName, isAdmin } from "@/utils/users"; import { convertToUsers, getGroupsForUser } from "@/utils/groups.be"; import { countEntityUsers, getEntityUsers, getSpecificUsers, getUsers } from "@/utils/users.be"; import { checkAccess, findAllowedEntities, getTypesOfUser } from "@/utils/permissions"; @@ -64,7 +64,7 @@ export default function Home({ user, entities }: Props) { Members - {count} + {count}{isAdmin(user) && ` / ${entity.licenses || 0}`} {users.map(getUserName).join(", ")}{' '}