misc: migrated groups API to use ids instead of slug

This commit is contained in:
Sheen Capadngan
2024-09-20 13:30:38 +08:00
parent fbebeaf38f
commit 93b7f56337
10 changed files with 99 additions and 78 deletions
+22 -21
View File
@@ -10,7 +10,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/", url: "/",
method: "POST", method: "POST",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
body: z.object({ body: z.object({
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name), name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
@@ -43,12 +43,12 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/:currentSlug", url: "/:id",
method: "PATCH", method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
params: z.object({ params: z.object({
currentSlug: z.string().trim().describe(GROUPS.UPDATE.currentSlug) id: z.string().trim().describe(GROUPS.UPDATE.id)
}), }),
body: z body: z
.object({ .object({
@@ -70,7 +70,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}, },
handler: async (req) => { handler: async (req) => {
const group = await server.services.group.updateGroup({ const group = await server.services.group.updateGroup({
currentSlug: req.params.currentSlug, id: req.params.id,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -83,12 +83,12 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/:slug", url: "/:id",
method: "DELETE", method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
params: z.object({ params: z.object({
slug: z.string().trim().describe(GROUPS.DELETE.slug) id: z.string().trim().describe(GROUPS.DELETE.id)
}), }),
response: { response: {
200: GroupsSchema 200: GroupsSchema
@@ -96,7 +96,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}, },
handler: async (req) => { handler: async (req) => {
const group = await server.services.group.deleteGroup({ const group = await server.services.group.deleteGroup({
groupSlug: req.params.slug, id: req.params.id,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -109,11 +109,11 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "GET", method: "GET",
url: "/:slug/users", url: "/:id/users",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
params: z.object({ params: z.object({
slug: z.string().trim().describe(GROUPS.LIST_USERS.slug) id: z.string().trim().describe(GROUPS.LIST_USERS.id)
}), }),
querystring: z.object({ querystring: z.object({
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset), offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
@@ -141,24 +141,25 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}, },
handler: async (req) => { handler: async (req) => {
const { users, totalCount } = await server.services.group.listGroupUsers({ const { users, totalCount } = await server.services.group.listGroupUsers({
groupSlug: req.params.slug, id: req.params.id,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.query ...req.query
}); });
return { users, totalCount }; return { users, totalCount };
} }
}); });
server.route({ server.route({
method: "POST", method: "POST",
url: "/:slug/users/:username", url: "/:id/users/:username",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
params: z.object({ params: z.object({
slug: z.string().trim().describe(GROUPS.ADD_USER.slug), id: z.string().trim().describe(GROUPS.ADD_USER.id),
username: z.string().trim().describe(GROUPS.ADD_USER.username) username: z.string().trim().describe(GROUPS.ADD_USER.username)
}), }),
response: { response: {
@@ -173,7 +174,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}, },
handler: async (req) => { handler: async (req) => {
const user = await server.services.group.addUserToGroup({ const user = await server.services.group.addUserToGroup({
groupSlug: req.params.slug, id: req.params.id,
username: req.params.username, username: req.params.username,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@@ -187,11 +188,11 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "DELETE", method: "DELETE",
url: "/:slug/users/:username", url: "/:id/users/:username",
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
params: z.object({ params: z.object({
slug: z.string().trim().describe(GROUPS.DELETE_USER.slug), id: z.string().trim().describe(GROUPS.DELETE_USER.id),
username: z.string().trim().describe(GROUPS.DELETE_USER.username) username: z.string().trim().describe(GROUPS.DELETE_USER.username)
}), }),
response: { response: {
@@ -206,7 +207,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
}, },
handler: async (req) => { handler: async (req) => {
const user = await server.services.group.removeUserFromGroup({ const user = await server.services.group.removeUserFromGroup({
groupSlug: req.params.slug, id: req.params.id,
username: req.params.username, username: req.params.username,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
+18 -24
View File
@@ -95,7 +95,7 @@ export const groupServiceFactory = ({
}; };
const updateGroup = async ({ const updateGroup = async ({
currentSlug, id,
name, name,
slug, slug,
role, role,
@@ -121,8 +121,10 @@ export const groupServiceFactory = ({
message: "Failed to update group due to plan restrictio Upgrade plan to update group." message: "Failed to update group due to plan restrictio Upgrade plan to update group."
}); });
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: currentSlug }); const group = await groupDAL.findOne({ orgId: actorOrgId, id });
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${currentSlug}` }); if (!group) {
throw new BadRequestError({ message: `Failed to find group with ID ${id}` });
}
let customRole: TOrgRoles | undefined; let customRole: TOrgRoles | undefined;
if (role) { if (role) {
@@ -140,8 +142,7 @@ export const groupServiceFactory = ({
const [updatedGroup] = await groupDAL.update( const [updatedGroup] = await groupDAL.update(
{ {
orgId: actorOrgId, id: group.id
slug: currentSlug
}, },
{ {
name, name,
@@ -158,7 +159,7 @@ export const groupServiceFactory = ({
return updatedGroup; return updatedGroup;
}; };
const deleteGroup = async ({ groupSlug, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => { const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" }); if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
@@ -178,15 +179,15 @@ export const groupServiceFactory = ({
}); });
const [group] = await groupDAL.delete({ const [group] = await groupDAL.delete({
orgId: actorOrgId, id,
slug: groupSlug orgId: actorOrgId
}); });
return group; return group;
}; };
const listGroupUsers = async ({ const listGroupUsers = async ({
groupSlug, id,
offset, offset,
limit, limit,
username, username,
@@ -208,12 +209,12 @@ export const groupServiceFactory = ({
const group = await groupDAL.findOne({ const group = await groupDAL.findOne({
orgId: actorOrgId, orgId: actorOrgId,
slug: groupSlug id
}); });
if (!group) if (!group)
throw new BadRequestError({ throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}` message: `Failed to find group with ID ${id}`
}); });
const users = await groupDAL.findAllGroupMembers({ const users = await groupDAL.findAllGroupMembers({
@@ -229,14 +230,7 @@ export const groupServiceFactory = ({
return { users, totalCount: count }; return { users, totalCount: count };
}; };
const addUserToGroup = async ({ const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
groupSlug,
username,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TAddUserToGroupDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" }); if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
@@ -251,12 +245,12 @@ export const groupServiceFactory = ({
// check if group with slug exists // check if group with slug exists
const group = await groupDAL.findOne({ const group = await groupDAL.findOne({
orgId: actorOrgId, orgId: actorOrgId,
slug: groupSlug id
}); });
if (!group) if (!group)
throw new BadRequestError({ throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}` message: `Failed to find group with ID ${id}`
}); });
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId); const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
@@ -285,7 +279,7 @@ export const groupServiceFactory = ({
}; };
const removeUserFromGroup = async ({ const removeUserFromGroup = async ({
groupSlug, id,
username, username,
actor, actor,
actorId, actorId,
@@ -306,12 +300,12 @@ export const groupServiceFactory = ({
// check if group with slug exists // check if group with slug exists
const group = await groupDAL.findOne({ const group = await groupDAL.findOne({
orgId: actorOrgId, orgId: actorOrgId,
slug: groupSlug id
}); });
if (!group) if (!group)
throw new BadRequestError({ throw new BadRequestError({
message: `Failed to find group with slug ${groupSlug}` message: `Failed to find group with ID ${id}`
}); });
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId); const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
+5 -5
View File
@@ -17,7 +17,7 @@ export type TCreateGroupDTO = {
} & TGenericPermission; } & TGenericPermission;
export type TUpdateGroupDTO = { export type TUpdateGroupDTO = {
currentSlug: string; id: string;
} & Partial<{ } & Partial<{
name: string; name: string;
slug: string; slug: string;
@@ -26,23 +26,23 @@ export type TUpdateGroupDTO = {
TGenericPermission; TGenericPermission;
export type TDeleteGroupDTO = { export type TDeleteGroupDTO = {
groupSlug: string; id: string;
} & TGenericPermission; } & TGenericPermission;
export type TListGroupUsersDTO = { export type TListGroupUsersDTO = {
groupSlug: string; id: string;
offset: number; offset: number;
limit: number; limit: number;
username?: string; username?: string;
} & TGenericPermission; } & TGenericPermission;
export type TAddUserToGroupDTO = { export type TAddUserToGroupDTO = {
groupSlug: string; id: string;
username: string; username: string;
} & TGenericPermission; } & TGenericPermission;
export type TRemoveUserFromGroupDTO = { export type TRemoveUserFromGroupDTO = {
groupSlug: string; id: string;
username: string; username: string;
} & TGenericPermission; } & TGenericPermission;
+5 -4
View File
@@ -5,26 +5,27 @@ export const GROUPS = {
role: "The role of the group to create." role: "The role of the group to create."
}, },
UPDATE: { UPDATE: {
currentSlug: "The current slug of the group to update.", id: "The id of the group to update",
name: "The new name of the group to update to.", name: "The new name of the group to update to.",
slug: "The new slug of the group to update to.", slug: "The new slug of the group to update to.",
role: "The new role of the group to update to." role: "The new role of the group to update to."
}, },
DELETE: { DELETE: {
id: "The id of the group to delete",
slug: "The slug of the group to delete" slug: "The slug of the group to delete"
}, },
LIST_USERS: { LIST_USERS: {
slug: "The slug of the group to list users for", id: "The id of the group to list users for",
offset: "The offset to start from. If you enter 10, it will start from the 10th user.", offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
limit: "The number of users to return.", limit: "The number of users to return.",
username: "The username to search for." username: "The username to search for."
}, },
ADD_USER: { ADD_USER: {
slug: "The slug of the group to add the user to.", id: "The id of the group to add the user to.",
username: "The username of the user to add to the group." username: "The username of the user to add to the group."
}, },
DELETE_USER: { DELETE_USER: {
slug: "The slug of the group to remove the user from.", id: "The id of the group to remove the user from.",
username: "The username of the user to remove from the group." username: "The username of the user to remove from the group."
} }
} as const; } as const;
+25 -9
View File
@@ -38,17 +38,17 @@ export const useUpdateGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
currentSlug, id,
name, name,
slug, slug,
role role
}: { }: {
currentSlug: string; id: string;
name?: string; name?: string;
slug?: string; slug?: string;
role?: string; role?: string;
}) => { }) => {
const { data: group } = await apiRequest.patch<TGroup>(`/api/v1/groups/${currentSlug}`, { const { data: group } = await apiRequest.patch<TGroup>(`/api/v1/groups/${id}`, {
name, name,
slug, slug,
role role
@@ -65,8 +65,8 @@ export const useUpdateGroup = () => {
export const useDeleteGroup = () => { export const useDeleteGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ slug }: { slug: string }) => { mutationFn: async ({ id }: { id: string }) => {
const { data: group } = await apiRequest.delete<TGroup>(`/api/v1/groups/${slug}`); const { data: group } = await apiRequest.delete<TGroup>(`/api/v1/groups/${id}`);
return group; return group;
}, },
@@ -79,8 +79,15 @@ export const useDeleteGroup = () => {
export const useAddUserToGroup = () => { export const useAddUserToGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ slug, username }: { slug: string; username: string }) => { mutationFn: async ({
const { data } = await apiRequest.post<TGroup>(`/api/v1/groups/${slug}/users/${username}`); groupId,
username
}: {
groupId: string;
username: string;
slug: string;
}) => {
const { data } = await apiRequest.post<TGroup>(`/api/v1/groups/${groupId}/users/${username}`);
return data; return data;
}, },
@@ -93,8 +100,17 @@ export const useAddUserToGroup = () => {
export const useRemoveUserFromGroup = () => { export const useRemoveUserFromGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ slug, username }: { slug: string; username: string }) => { mutationFn: async ({
const { data } = await apiRequest.delete<TGroup>(`/api/v1/groups/${slug}/users/${username}`); username,
groupId
}: {
slug: string;
username: string;
groupId: string;
}) => {
const { data } = await apiRequest.delete<TGroup>(
`/api/v1/groups/${groupId}/users/${username}`
);
return data; return data;
}, },
+10 -6
View File
@@ -4,7 +4,8 @@ import { apiRequest } from "@app/config/request";
export const groupKeys = { export const groupKeys = {
allGroupUserMemberships: () => ["group-user-memberships"] as const, allGroupUserMemberships: () => ["group-user-memberships"] as const,
forGroupUserMemberships: (slug: string) => [...groupKeys.allGroupUserMemberships(), slug] as const, forGroupUserMemberships: (slug: string) =>
[...groupKeys.allGroupUserMemberships(), slug] as const,
specificGroupUserMemberships: ({ specificGroupUserMemberships: ({
slug, slug,
offset, offset,
@@ -28,11 +29,13 @@ type TUser = {
}; };
export const useListGroupUsers = ({ export const useListGroupUsers = ({
id,
groupSlug, groupSlug,
offset = 0, offset = 0,
limit = 10, limit = 10,
username username
}: { }: {
id: string;
groupSlug: string; groupSlug: string;
offset: number; offset: number;
limit: number; limit: number;
@@ -52,14 +55,15 @@ export const useListGroupUsers = ({
limit: String(limit), limit: String(limit),
username username
}); });
const { data } = await apiRequest.get<{ users: TUser[]; totalCount: number; }>( const { data } = await apiRequest.get<{ users: TUser[]; totalCount: number }>(
`/api/v1/groups/${groupSlug}/users`, { `/api/v1/groups/${id}/users`,
{
params params
} }
); );
return data; return data;
}, }
}); });
}; };
@@ -35,10 +35,12 @@ export const OrgGroupMembersModal = ({ popUp, handlePopUpToggle }: Props) => {
const [searchMemberFilter, setSearchMemberFilter] = useState(""); const [searchMemberFilter, setSearchMemberFilter] = useState("");
const popUpData = popUp?.groupMembers?.data as { const popUpData = popUp?.groupMembers?.data as {
groupId: string;
slug: string; slug: string;
}; };
const { data, isLoading } = useListGroupUsers({ const { data, isLoading } = useListGroupUsers({
id: popUpData?.groupId,
groupSlug: popUpData?.slug, groupSlug: popUpData?.slug,
offset: (page - 1) * perPage, offset: (page - 1) * perPage,
limit: perPage, limit: perPage,
@@ -54,11 +56,13 @@ export const OrgGroupMembersModal = ({ popUp, handlePopUpToggle }: Props) => {
if (assign) { if (assign) {
await assignMutateAsync({ await assignMutateAsync({
groupId: popUpData.groupId,
username, username,
slug: popUpData.slug slug: popUpData.slug
}); });
} else { } else {
await unassignMutateAsync({ await unassignMutateAsync({
groupId: popUpData.groupId,
username, username,
slug: popUpData.slug slug: popUpData.slug
}); });
@@ -85,7 +85,7 @@ export const OrgGroupModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Pr
if (group) { if (group) {
await updateMutateAsync({ await updateMutateAsync({
currentSlug: group.slug, id: group.groupId,
name, name,
slug, slug,
role: role || undefined role: role || undefined
@@ -34,10 +34,10 @@ export const OrgGroupsSection = () => {
} }
}; };
const onDeleteGroupSubmit = async ({ name, slug }: { name: string; slug: string }) => { const onDeleteGroupSubmit = async ({ name, groupId }: { name: string; groupId: string }) => {
try { try {
await deleteMutateAsync({ await deleteMutateAsync({
slug id: groupId
}); });
createNotification({ createNotification({
text: `Successfully deleted the group named ${name}`, text: `Successfully deleted the group named ${name}`,
@@ -87,7 +87,7 @@ export const OrgGroupsSection = () => {
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)} onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
deleteKey="confirm" deleteKey="confirm"
onDeleteApproved={() => onDeleteApproved={() =>
onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; slug: string }) onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; groupId: string })
} }
/> />
<UpgradePlanModal <UpgradePlanModal
@@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { faEllipsis,faMagnifyingGlass, faUsers } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis, faMagnifyingGlass, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -52,10 +52,10 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
const { data: roles } = useGetOrgRoles(orgId); const { data: roles } = useGetOrgRoles(orgId);
const handleChangeRole = async ({ currentSlug, role }: { currentSlug: string; role: string }) => { const handleChangeRole = async ({ id, role }: { id: string; role: string }) => {
try { try {
await updateMutateAsync({ await updateMutateAsync({
currentSlug, id,
role role
}); });
@@ -112,7 +112,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800" dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) => onValueChange={(selectedRole) =>
handleChangeRole({ handleChangeRole({
currentSlug: slug, id,
role: selectedRole role: selectedRole
}) })
} }
@@ -147,6 +147,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handlePopUpOpen("groupMembers", { handlePopUpOpen("groupMembers", {
groupId: id,
slug slug
}); });
}} }}
@@ -195,7 +196,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handlePopUpOpen("deleteGroup", { handlePopUpOpen("deleteGroup", {
slug, groupId: id,
name name
}); });
}} }}