feat(infisical-pg): new org role routes completed

This commit is contained in:
Akhil Mohan
2023-12-11 16:20:32 +05:30
parent 9a4b2f7d81
commit 3670b16657
13 changed files with 157 additions and 42 deletions

View File

@@ -19,7 +19,8 @@
"migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
"migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback",
"seed:new": "tsx ./scripts/create-seed-file.ts",
"seed:run": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run"
"seed:run": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest && npm run seed:run"
},
"keywords": [],
"author": "",

View File

@@ -12,7 +12,7 @@ export async function up(knex: Knex): Promise<void> {
t.string("name").notNullable();
t.string("description");
t.string("slug").notNullable();
t.json("permissions").notNullable();
t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();

View File

@@ -17,7 +17,6 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
name: z.string().trim(),
description: z.string().trim().optional(),
workspaceId: z.string().trim().optional(),
orgId: z.string().trim(),
permissions: z.any().array()
}),
response: {
@@ -31,7 +30,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
const role = await server.services.orgRole.createRole(
req.auth.userId,
req.params.organizationId,
{ ...req.body, permissions: JSON.stringify(req.body.permissions) }
req.body
);
return { role };
}
@@ -49,8 +48,6 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
slug: z.string().trim().optional(),
name: z.string().trim().optional(),
description: z.string().trim().optional(),
workspaceId: z.string().trim().optional(),
orgId: z.string().trim(),
permissions: z.any().array()
}),
response: {
@@ -108,9 +105,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
roles: OrgRolesSchema.omit({ permissions: true })
.merge(z.object({ permissions: z.unknown() }))
.array()
data: z.object({
roles: OrgRolesSchema.omit({ permissions: true })
.merge(z.object({ permissions: z.unknown() }))
.array()
})
})
}
},
@@ -120,7 +119,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
req.auth.userId,
req.params.organizationId
);
return { roles };
return { data: { roles } };
}
});

View File

@@ -36,8 +36,11 @@ export const orgRoleServiceFactory = ({
);
const existingRole = await orgRoleDal.findOne({ slug: data.slug, orgId });
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
const role = await orgRoleDal.create({ ...data, orgId });
const role = await orgRoleDal.create({
...data,
orgId,
permissions: JSON.stringify(data.permissions)
});
return role;
};
@@ -83,8 +86,8 @@ export const orgRoleServiceFactory = ({
const customRoles = await orgRoleDal.find({ orgId });
const roles = [
{
id: "admin",
orgId: "",
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // dummy userid
orgId,
name: "Admin",
slug: "admin",
description: "Complete administration access over the organization",
@@ -93,8 +96,8 @@ export const orgRoleServiceFactory = ({
updatedAt: new Date()
},
{
id: "member",
orgId: "",
id: "b11b49a9-09a9-4443-916a-4246f9ff2c70", // dummy user for zod validation in response
orgId,
name: "Member",
slug: "member",
description: "Non-administrative role in an organization",

View File

@@ -1,2 +1,14 @@
export { useCreateRole, useDeleteRole, useUpdateRole } from "./mutation";
export { useGetRoles, useGetUserOrgPermissions,useGetUserProjectPermissions } from "./queries";
export {
useCreateOrgRole,
useCreateRole,
useDeleteOrgRole,
useDeleteRole,
useUpdateOrgRole,
useUpdateRole
} from "./mutation";
export {
useGetOrgRoles,
useGetRoles,
useGetUserOrgPermissions,
useGetUserProjectPermissions
} from "./queries";

View File

@@ -1,9 +1,17 @@
import { packRules } from "@casl/ability/extra";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { roleQueryKeys } from "./queries";
import { TCreateRoleDTO, TDeleteRoleDTO, TUpdateRoleDTO } from "./types";
import {
TCreateOrgRoleDTO,
TCreateRoleDTO,
TDeleteOrgRoleDTO,
TDeleteRoleDTO,
TUpdateOrgRoleDTO,
TUpdateRoleDTO
} from "./types";
export const useCreateRole = <T extends string | undefined>() => {
const queryClient = useQueryClient();
@@ -40,3 +48,47 @@ export const useDeleteRole = () => {
}
});
};
export const useCreateOrgRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ orgId, permissions, ...dto }: TCreateOrgRoleDTO) =>
apiRequest.post(`/api/ee/v1/organization/${orgId}/roles`, {
...dto,
permissions: permissions.length ? packRules(permissions) : []
}),
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId));
}
});
};
export const useUpdateOrgRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) =>
apiRequest.patch(`/api/ee/v1/organization/${orgId}/roles/${id}`, {
...dto,
permissions: permissions?.length ? packRules(permissions) : undefined
}),
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId));
}
});
};
export const useDeleteOrgRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ orgId, id }: TDeleteOrgRoleDTO) =>
apiRequest.delete(`/api/ee/v1/organization/${orgId}/roles/${id}`, {
data: { orgId }
}),
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(roleQueryKeys.getOrgRoles(orgId));
}
});
};

View File

@@ -13,6 +13,8 @@ import {
TGetRolesDTO,
TGetUserOrgPermissionsDTO,
TGetUserProjectPermissionDTO,
TOrgRole,
TPermission,
TRole
} from "./types";
@@ -36,6 +38,7 @@ const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
export const roleQueryKeys = {
getRoles: ({ orgId, workspaceId }: TGetRolesDTO) => ["roles", { orgId, workspaceId }] as const,
getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const,
getUserOrgPermissions: ({ orgId }: TGetUserOrgPermissionsDTO) =>
["user-permissions", { orgId }] as const,
getUserProjectPermissions: ({ workspaceId }: TGetUserProjectPermissionDTO) =>
@@ -63,6 +66,23 @@ export const useGetRoles = ({ orgId, workspaceId }: TGetRolesDTO) =>
enabled: Boolean(orgId)
});
const getOrgRoles = async (orgId: string) => {
const { data } = await apiRequest.get<{
data: { roles: Array<Omit<TOrgRole, "permissions"> & { permissions: unknown }> };
}>(`/api/ee/v1/organization/${orgId}/roles`);
return data.data.roles.map(({ permissions, ...el }) => ({
...el,
permissions: unpackRules(permissions as PackRule<TPermission>[])
}));
};
export const useGetOrgRoles = (orgId: string, enable = true) =>
useQuery({
queryKey: roleQueryKeys.getOrgRoles(orgId),
queryFn: () => getOrgRoles(orgId),
enabled: Boolean(orgId) && enable
});
const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => {
if (orgId === "") return { permissions: [], membership: null };

View File

@@ -3,6 +3,7 @@ export type TGetRolesDTO = {
workspaceId?: string;
};
// @depreciated
export type TRole<T extends string | undefined> = {
id: string;
organization: string;
@@ -15,6 +16,17 @@ export type TRole<T extends string | undefined> = {
updatedAt: string;
};
export type TOrgRole = {
slug: string;
name: string;
orgId: string;
id: string;
createdAt: string;
updatedAt: string;
description?: string;
permissions: TPermission[];
};
export type TPermission = {
conditions?: Record<string, any>;
action: string;
@@ -55,3 +67,21 @@ export type TGetUserOrgPermissionsDTO = {
export type TGetUserProjectPermissionDTO = {
workspaceId: string;
};
export type TCreateOrgRoleDTO = {
orgId: string;
name: string;
description?: string;
slug: string;
permissions: TPermission[];
};
export type TUpdateOrgRoleDTO = {
orgId: string;
id: string;
} & Partial<Omit<TCreateOrgRoleDTO, "orgId">>;
export type TDeleteOrgRoleDTO = {
orgId: string;
id: string;
};

View File

@@ -30,6 +30,7 @@ import {
import {
useAddUserToOrg,
useFetchServerStatus,
useGetOrgRoles,
useGetOrgUsers,
useGetRoles,
useUpdateOrgUserRole
@@ -56,9 +57,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
const userId = user?.id || "";
const orgId = currentOrg?.id || "";
const { data: roles, isLoading: isRolesLoading } = useGetRoles({
orgId
});
const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId);
const [searchMemberFilter, setSearchMemberFilter] = useState("");

View File

@@ -8,15 +8,16 @@ import {
faServer,
faSignIn,
faUserCog,
faUsers} from "@fortawesome/free-solid-svg-icons";
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { Button, FormControl, Input } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { useCreateRole, useUpdateRole } from "@app/hooks/api";
import { TRole } from "@app/hooks/api/roles/types";
import { useCreateOrgRole, useUpdateOrgRole } from "@app/hooks/api";
import { TOrgRole } from "@app/hooks/api/roles/types";
import {
formRolePermission2API,
@@ -28,7 +29,7 @@ import { SimpleLevelPermissionOption } from "./SimpleLevelPermissionOptions";
import { WorkspacePermission } from "./WorkspacePermission";
type Props = {
role?: TRole<undefined>;
role?: TOrgRole;
onGoBack: VoidFunction;
};
@@ -101,8 +102,8 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
resolver: zodResolver(formSchema)
});
const { mutateAsync: createRole } = useCreateRole();
const { mutateAsync: updateRole } = useUpdateRole();
const { mutateAsync: createRole } = useCreateOrgRole();
const { mutateAsync: updateRole } = useUpdateOrgRole();
const handleRoleUpdate = async (el: TFormSchema) => {
if (!role?.id) return;

View File

@@ -32,7 +32,7 @@ export const formSchema = z.object({
"secret-scanning": generalPermissionSchema,
sso: generalPermissionSchema,
billing: generalPermissionSchema,
"identity": generalPermissionSchema
identity: generalPermissionSchema
})
.optional()
});

View File

@@ -1,7 +1,7 @@
import { motion } from "framer-motion";
import { usePopUp } from "@app/hooks";
import { TRole } from "@app/hooks/api/roles/types";
import { TOrgRole } from "@app/hooks/api/roles/types";
import { OrgRoleModifySection } from "./OrgRoleModifySection";
import { OrgRoleTable } from "./OrgRoleTable";
@@ -17,7 +17,7 @@ export const OrgRoleTabSection = () => {
exit={{ opacity: 0, translateX: 30 }}
>
<OrgRoleModifySection
role={popUp.editRole.data as TRole<undefined>}
role={popUp.editRole.data as TOrgRole}
onGoBack={() => handlePopUpClose("editRole")}
/>
</motion.div>

View File

@@ -20,11 +20,11 @@ import {
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteRole, useGetRoles } from "@app/hooks/api";
import { TRole } from "@app/hooks/api/roles/types";
import { useDeleteOrgRole, useGetOrgRoles } from "@app/hooks/api";
import { TOrgRole } from "@app/hooks/api/roles/types";
type Props = {
onSelectRole: (role?: TRole<undefined>) => void;
onSelectRole: (role?: TOrgRole) => void;
};
export const OrgRoleTable = ({ onSelectRole }: Props) => {
@@ -34,14 +34,12 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => {
const { createNotification } = useNotificationContext();
const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const);
const { data: roles, isLoading: isRolesLoading } = useGetRoles({
orgId
});
const { data: roles, isLoading: isRolesLoading } = useGetOrgRoles(orgId);
const { mutateAsync: deleteRole } = useDeleteRole();
const { mutateAsync: deleteRole } = useDeleteOrgRole();
const handleRoleDelete = async () => {
const { id } = popUp?.deleteRole?.data as TRole<undefined>;
const { id } = popUp?.deleteRole?.data as TOrgRole;
try {
await deleteRole({
orgId,
@@ -90,7 +88,7 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => {
</THead>
<TBody>
{isRolesLoading && <TableSkeleton columns={4} innerKey="org-roles" />}
{(roles as TRole<undefined>[])?.map((role) => {
{roles?.map((role) => {
const { id, name, slug } = role;
const isNonMutatable = ["owner", "admin", "member", "no-access"].includes(slug);
@@ -148,9 +146,9 @@ export const OrgRoleTable = ({ onSelectRole }: Props) => {
<DeleteActionModal
isOpen={popUp.deleteRole.isOpen}
title={`Are you sure want to delete ${
(popUp?.deleteRole?.data as TRole<undefined>)?.name || " "
(popUp?.deleteRole?.data as TOrgRole)?.name || " "
} role?`}
deleteKey={(popUp?.deleteRole?.data as TRole<undefined>)?.slug || ""}
deleteKey={(popUp?.deleteRole?.data as TOrgRole)?.slug || ""}
onClose={() => handlePopUpClose("deleteRole")}
onDeleteApproved={handleRoleDelete}
/>