Add API specs to groups endpoints, convert projectId groups endpoints to be slug-based

This commit is contained in:
Tuan Dang
2024-03-28 18:21:12 -07:00
parent 8afecac7d8
commit 9460eafd91
18 changed files with 213 additions and 146 deletions

View File

@@ -2,6 +2,7 @@ import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
import { GROUPS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -12,8 +13,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
// TODO: update on frontend to not send organizationId
name: z.string().trim().min(1),
name: z.string().trim().min(1).describe(GROUPS.CREATE.name),
slug: z
.string()
.min(5)
@@ -21,8 +21,9 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
})
.optional(),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
.optional()
.describe(GROUPS.CREATE.slug),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(GROUPS.CREATE.role)
}),
response: {
200: GroupsSchema
@@ -47,19 +48,20 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
currentSlug: z.string().trim()
currentSlug: z.string().trim().describe(GROUPS.UPDATE.currentSlug)
}),
body: z
.object({
name: z.string().trim().min(1),
name: z.string().trim().min(1).describe(GROUPS.UPDATE.name),
slug: z
.string()
.min(5)
.max(36)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid slug"
}),
role: z.string().trim().min(1)
})
.describe(GROUPS.UPDATE.slug),
role: z.string().trim().min(1).describe(GROUPS.UPDATE.role)
})
.partial(),
response: {
@@ -81,12 +83,12 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
});
server.route({
url: "/:groupSlug",
url: "/:slug",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
groupSlug: z.string().trim()
slug: z.string().trim().describe(GROUPS.DELETE.slug)
}),
response: {
200: GroupsSchema
@@ -94,7 +96,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const group = await server.services.group.deleteGroup({
groupSlug: req.params.groupSlug,
groupSlug: req.params.slug,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -111,7 +113,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim()
slug: z.string().trim().describe(GROUPS.LIST_USERS.slug)
}),
response: {
200: UsersSchema.pick({
@@ -147,8 +149,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim(),
username: z.string().trim()
slug: z.string().trim().describe(GROUPS.ADD_USER.slug),
username: z.string().trim().describe(GROUPS.ADD_USER.username)
}),
response: {
200: UsersSchema.pick({
@@ -180,8 +182,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
slug: z.string().trim(),
username: z.string().trim()
slug: z.string().trim().describe(GROUPS.DELETE_USER.slug),
username: z.string().trim().describe(GROUPS.DELETE_USER.username)
}),
response: {
200: UsersSchema.pick({

View File

@@ -30,7 +30,7 @@ import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
type TGroupServiceFactoryDep = {
userDAL: Pick<TUserDALFactory, "findOne" | "findUserEncKeyByUsername">;
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "update" | "delete" | "findAllGroupMembers">;
groupProjectDAL: TGroupProjectDALFactory;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgDAL: Pick<TOrgDALFactory, "findMembership">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,

View File

@@ -112,6 +112,7 @@ const buildMemberPermission = () => {
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);

View File

@@ -145,41 +145,6 @@ export const permissionDALFactory = (db: TDbClient) => {
]
});
// const permission = sqlNestRelationships({
// data: docs, // TODO: concat?
// key: "membershipId",
// parentMapper: ({
// orgId,
// orgAuthEnforced,
// membershipId,
// membershipCreatedAt,
// membershipUpdatedAt,
// oldRoleField,
// // role
// }) => ({
// orgId,
// orgAuthEnforced,
// userId,
// role: oldRoleField,
// // role,
// id: membershipId,
// projectId,
// createdAt: membershipCreatedAt,
// updatedAt: membershipUpdatedAt
// }),
// childrenMapper: [
// {
// key: "id",
// label: "roles" as const,
// mapper: (data) =>
// ProjectUserMembershipRolesSchema.extend({
// permissions: z.unknown(),
// customRoleSlug: z.string().optional().nullable()
// }).parse(data)
// }
// ]
// });
// when introducting cron mode change it here
const activeRoles = permission?.[0]?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>

View File

@@ -1,3 +1,31 @@
export const GROUPS = {
CREATE: {
name: "The name of the group to create.",
slug: "The slug of the group to create.",
role: "The role of the group to create."
},
UPDATE: {
currentSlug: "The current slug of the group to update.",
name: "The new name 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."
},
DELETE: {
slug: "The slug of the group to delete"
},
LIST_USERS: {
slug: "The slug of the group to list users for"
},
ADD_USER: {
slug: "The slug of the group to add the user to.",
username: "The username of the user to add to the group."
},
DELETE_USER: {
slug: "The slug of the group to remove the user from.",
username: "The username of the user to remove from the group."
}
} as const;
export const IDENTITIES = {
CREATE: {
name: "The name of the identity to create.",
@@ -79,6 +107,9 @@ export const ORGANIZATIONS = {
},
GET_PROJECTS: {
organizationId: "The ID of the organization to get projects from."
},
LIST_GROUPS: {
organizationId: "The ID of the organization to list groups for."
}
} as const;
@@ -141,6 +172,23 @@ export const PROJECTS = {
},
ROLLBACK_TO_SNAPSHOT: {
secretSnapshotId: "The ID of the snapshot to rollback to."
},
ADD_GROUP_TO_PROJECT: {
projectSlug: "The slug of the project to add the group to.",
groupSlug: "The slug of the group to add to the project.",
role: "The role for the group to assume in the project."
},
UPDATE_GROUP_IN_PROJECT: {
projectSlug: "The slug of the project to update the group in.",
groupSlug: "The slug of the group to update in the project.",
roles: "A list of roles to update the group to."
},
REMOVE_GROUP_FROM_PROJECT: {
projectSlug: "The slug of the project to delete the group from.",
groupSlug: "The slug of the group to delete from the project."
},
LIST_GROUPS_IN_PROJECT: {
projectSlug: "The slug of the project to list groups for."
}
} as const;

View File

@@ -15,6 +15,15 @@ export type TProjectPermission = {
actorOrgId: string;
};
// same as TProjectPermission but with projectSlug requirement instead of projectId
export type TProjectSlugPermission = {
actor: ActorType;
actorId: string;
projectSlug: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string;
};
export type RequiredKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[keyof T];

View File

@@ -8,6 +8,7 @@ import {
OrgRolesSchema,
UsersSchema
} from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -209,7 +210,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
url: "/:organizationId/groups",
schema: {
params: z.object({
organizationId: z.string().trim()
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_GROUPS.organizationId)
}),
response: {
200: z.object({
@@ -232,7 +233,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
const groups = await server.services.org.getOrgGroups({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.permission.orgId,
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});

View File

@@ -15,15 +15,26 @@ import { ProjectUserMembershipTemporaryMode } from "@app/services/project-member
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/group-memberships/:groupSlug",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Add group to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
groupSlug: z.string().trim()
projectSlug: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupSlug)
}),
body: z.object({
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
role: z
.string()
.trim()
.min(1)
.default(ProjectMembershipRole.NoAccess)
.describe(PROJECTS.ADD_GROUP_TO_PROJECT.role)
}),
response: {
200: z.object({
@@ -32,13 +43,13 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.createProjectGroup({
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectId: req.params.projectId,
projectSlug: req.params.projectSlug,
role: req.body.role
});
return { groupMembership };
@@ -47,18 +58,18 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
server.route({
method: "PATCH",
url: "/:projectId/group-memberships/:groupSlug",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update project group memberships",
description: "Update group in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
groupSlug: z.string().trim()
projectSlug: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.groupSlug)
}),
body: z.object({
roles: z
@@ -78,6 +89,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
])
)
.min(1)
.describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.roles)
}),
response: {
200: z.object({
@@ -86,13 +98,13 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateProjectGroup({
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectId: req.params.projectId,
projectSlug: req.params.projectSlug,
roles: req.body.roles
});
return { roles };
@@ -101,18 +113,18 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
server.route({
method: "DELETE",
url: "/:projectId/group-memberships/:groupSlug",
url: "/:projectSlug/groups/:groupSlug",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Delete project group memberships",
description: "Remove group from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
groupSlug: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
projectSlug: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.projectSlug),
groupSlug: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.groupSlug)
}),
response: {
200: z.object({
@@ -121,13 +133,13 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.deleteProjectGroup({
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupSlug: req.params.groupSlug,
projectId: req.params.projectId
projectSlug: req.params.projectSlug
});
return { groupMembership };
}
@@ -135,17 +147,17 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
server.route({
method: "GET",
url: "/:projectId/group-memberships",
url: "/:projectSlug/groups",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return project group memberships",
description: "Return list of groups in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
projectSlug: z.string().trim().describe(PROJECTS.LIST_GROUPS_IN_PROJECT.projectSlug)
}),
response: {
200: z.object({
@@ -176,12 +188,12 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listProjectGroup({
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
projectSlug: req.params.projectSlug
});
return { groupMemberships };
}

View File

@@ -33,7 +33,7 @@ type TGroupProjectServiceFactoryDep = {
"create" | "transaction" | "insertMany" | "delete"
>;
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "findProjectGhostUser">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany" | "transaction">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
projectBotDAL: TProjectBotDALFactory;
@@ -54,19 +54,25 @@ export const groupProjectServiceFactory = ({
projectRoleDAL,
permissionService
}: TGroupProjectServiceFactoryDep) => {
const createProjectGroup = async ({
const addGroupToProject = async ({
groupSlug,
actor,
actorId,
actorOrgId,
actorAuthMethod,
projectId,
projectSlug,
role
}: TCreateProjectGroupDTO) => {
const project = await projectDAL.findOne({
slug: projectSlug
});
if (!project) throw new BadRequestError({ message: `Failed to find project with slug ${projectSlug}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
project.id,
actorAuthMethod,
actorOrgId
);
@@ -75,14 +81,12 @@ export const groupProjectServiceFactory = ({
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: groupSlug });
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const existingGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId });
const existingGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (existingGroup)
throw new BadRequestError({
message: `Group with slug ${groupSlug} already exists in project with id ${projectId}`
message: `Group with slug ${groupSlug} already exists in project with id ${project.id}`
});
const project = await projectDAL.findById(projectId);
const { permission: rolePermission, role: customRole } = await permissionService.getProjectPermissionByRole(
role,
project.id
@@ -119,10 +123,10 @@ export const groupProjectServiceFactory = ({
// share project key with users in group that have not
// individually been added to the project and that are not part of
// other groups that are in the project
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, projectId);
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id);
if (groupMembers.length) {
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
const ghostUser = await projectDAL.findProjectGhostUser(project.id);
if (!ghostUser) {
throw new BadRequestError({
@@ -130,7 +134,7 @@ export const groupProjectServiceFactory = ({
});
}
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, project.id);
if (!ghostUserLatestKey) {
throw new BadRequestError({
@@ -138,7 +142,7 @@ export const groupProjectServiceFactory = ({
});
}
const bot = await projectBotDAL.findOne({ projectId });
const bot = await projectBotDAL.findOne({ projectId: project.id });
if (!bot) {
throw new BadRequestError({
@@ -168,7 +172,7 @@ export const groupProjectServiceFactory = ({
nonce,
senderId: ghostUser.id,
receiverId: id,
projectId
projectId: project.id
};
});
@@ -178,8 +182,8 @@ export const groupProjectServiceFactory = ({
return projectGroup;
};
const updateProjectGroup = async ({
projectId,
const updateGroupInProject = async ({
projectSlug,
groupSlug,
roles,
actor,
@@ -187,10 +191,16 @@ export const groupProjectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectGroupDTO) => {
const project = await projectDAL.findOne({
slug: projectSlug
});
if (!project) throw new BadRequestError({ message: `Failed to find project with slug ${projectSlug}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
project.id,
actorAuthMethod,
actorOrgId
);
@@ -199,12 +209,12 @@ export const groupProjectServiceFactory = ({
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: groupSlug });
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const projectGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId });
const projectGroup = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!projectGroup) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const { permission: groupRolePermission } = await permissionService.getProjectPermissionByRole(
projectGroup.role,
projectId
project.id
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
@@ -217,7 +227,7 @@ export const groupProjectServiceFactory = ({
const hasCustomRole = Boolean(customInputRoles.length);
const customRoles = hasCustomRole
? await projectRoleDAL.find({
projectId,
projectId: project.id,
$in: { slug: customInputRoles.map(({ role }) => role) }
})
: [];
@@ -257,69 +267,87 @@ export const groupProjectServiceFactory = ({
return updatedRoles;
};
const deleteProjectGroup = async ({
const removeGroupFromProject = async ({
projectSlug,
groupSlug,
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectId
actorAuthMethod
}: TDeleteProjectGroupDTO) => {
const project = await projectDAL.findOne({
slug: projectSlug
});
if (!project) throw new BadRequestError({ message: `Failed to find project with slug ${projectSlug}` });
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: groupSlug });
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId });
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!groupProjectMembership) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
project.id,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
const { permission: groupRolePermission } = await permissionService.getProjectPermissionByRole(
groupProjectMembership.role,
projectId
project.id
);
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
if (!hasRequiredPriviledges) throw new ForbiddenRequestError({ message: "Failed to delete more privileged group" });
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, projectId);
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id);
if (groupMembers.length) {
await projectKeyDAL.delete({
projectId,
projectId: project.id,
$in: {
receiverId: groupMembers.map(({ user: { id } }) => id)
}
});
}
const [deletedGroup] = await groupProjectDAL.delete({ groupId: group.id, projectId });
const [deletedGroup] = await groupProjectDAL.delete({ groupId: group.id, projectId: project.id });
return deletedGroup;
};
const listProjectGroup = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TListProjectGroupDTO) => {
const listGroupsInProject = async ({
projectSlug,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TListProjectGroupDTO) => {
const project = await projectDAL.findOne({
slug: projectSlug
});
if (!project) throw new BadRequestError({ message: `Failed to find project with slug ${projectSlug}` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
project.id,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
const groupMemberhips = await groupProjectDAL.findByProjectId(projectId);
const groupMemberhips = await groupProjectDAL.findByProjectId(project.id);
return groupMemberhips;
};
return {
createProjectGroup,
updateProjectGroup,
deleteProjectGroup,
listProjectGroup
addGroupToProject,
updateGroupInProject,
removeGroupFromProject,
listGroupsInProject
};
};

View File

@@ -1,11 +1,11 @@
import { TProjectPermission } from "@app/lib/types";
import { TProjectSlugPermission } from "@app/lib/types";
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
export type TCreateProjectGroupDTO = {
groupSlug: string;
role: string;
} & TProjectPermission;
} & TProjectSlugPermission;
export type TUpdateProjectGroupDTO = {
roles: (
@@ -22,10 +22,10 @@ export type TUpdateProjectGroupDTO = {
}
)[];
groupSlug: string;
} & TProjectPermission;
} & TProjectSlugPermission;
export type TDeleteProjectGroupDTO = {
groupSlug: string;
} & TProjectPermission;
} & TProjectSlugPermission;
export type TListProjectGroupDTO = TProjectPermission;
export type TListProjectGroupDTO = TProjectSlugPermission;

View File

@@ -118,7 +118,8 @@ export const orgServiceFactory = ({
};
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
const groups = await groupDAL.findByOrgId(orgId);
return groups;
};

View File

@@ -51,7 +51,10 @@ export type TFindAllWorkspacesDTO = {
};
export type TUpdateOrgDTO = {
orgId: string;
data: Partial<{ name: string; slug: string; authEnforced: boolean; scimEnabled: boolean }>;
} & TOrgPermission;
export type TGetOrgGroupsDTO = TOrgPermission;
export type TGetOrgGroupsDTO = {
orgId: string;
} & TOrgPermission;

View File

@@ -40,7 +40,7 @@ export const workspaceKeys = {
getWorkspaceIdentityMemberships: (workspaceId: string) =>
[{ workspaceId }, "workspace-identity-memberships"] as const,
getWorkspaceGroupMemberships: (workspaceId: string) =>
[{ workspaceId }, "workspace-group-memberships"] as const
[{ workspaceId }, "workspace-groups"] as const
};
const fetchWorkspaceById = async (workspaceId: string) => {
@@ -460,25 +460,25 @@ export const useAddGroupToWorkspace = () => {
return useMutation({
mutationFn: async ({
groupSlug,
workspaceId,
projectSlug,
role
}: {
groupSlug: string;
workspaceId: string;
projectSlug: string;
role?: string;
}) => {
const {
data: { groupMembership }
} = await apiRequest.post(
`/api/v2/workspace/${workspaceId}/group-memberships/${groupSlug}`,
`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`,
{
role
}
);
return groupMembership;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug));
}
});
};
@@ -486,20 +486,19 @@ export const useAddGroupToWorkspace = () => {
export const useUpdateGroupWorkspaceRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ groupSlug, workspaceId, roles }: TUpdateWorkspaceGroupRoleDTO) => {
mutationFn: async ({ groupSlug, projectSlug, roles }: TUpdateWorkspaceGroupRoleDTO) => {
const {
data: { groupMembership }
} = await apiRequest.patch(
`/api/v2/workspace/${workspaceId}/group-memberships/${groupSlug}`,
{
`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`, {
roles
}
);
return groupMembership;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug));
}
});
};
@@ -509,32 +508,32 @@ export const useDeleteGroupFromWorkspace = () => {
return useMutation({
mutationFn: async ({
groupSlug,
workspaceId
projectSlug
}: {
groupSlug: string;
workspaceId: string;
projectSlug: string;
}) => {
const {
data: { groupMembership }
} = await apiRequest.delete(
`/api/v2/workspace/${workspaceId}/group-memberships/${groupSlug}`
`/api/v2/workspace/${projectSlug}/groups/${groupSlug}`
);
return groupMembership;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceGroupMemberships(projectSlug));
}
});
};
export const useGetWorkspaceGroupMemberships = (workspaceId: string) => {
export const useGetWorkspaceGroupMemberships = (projectSlug: string) => {
return useQuery({
queryKey: workspaceKeys.getWorkspaceGroupMemberships(workspaceId),
queryKey: workspaceKeys.getWorkspaceGroupMemberships(projectSlug),
queryFn: async () => {
const {
data: { groupMemberships }
} = await apiRequest.get<{ groupMemberships: TGroupMembership[] }>(
`/api/v2/workspace/${workspaceId}/group-memberships`
`/api/v2/workspace/${projectSlug}/groups`
);
return groupMemberships;
},

View File

@@ -114,7 +114,7 @@ export type TUpdateWorkspaceIdentityRoleDTO = {
export type TUpdateWorkspaceGroupRoleDTO = {
groupSlug: string;
workspaceId: string;
projectSlug: string;
roles: (
| {
role: string;

View File

@@ -48,7 +48,7 @@ export const GroupModal = ({
const workspaceId = currentWorkspace?.id || "";
const { data: groups } = useGetOrganizationGroups(orgId);
const { data: groupMemberships } = useGetWorkspaceGroupMemberships(workspaceId);
const { data: groupMemberships } = useGetWorkspaceGroupMemberships(currentWorkspace?.slug || "");
const { data: roles } = useGetProjectRoles(workspaceId);
@@ -76,7 +76,7 @@ export const GroupModal = ({
const onFormSubmit = async ({ slug, role }: FormData) => {
try {
await addGroupToWorkspaceMutateAsync({
workspaceId,
projectSlug: currentWorkspace?.slug || "",
groupSlug: slug,
role: role || undefined
});

View File

@@ -252,7 +252,7 @@ export const GroupRoles = ({
try {
await updateGroupWorkspaceRole.mutateAsync({
workspaceId,
projectSlug: currentWorkspace?.slug || "",
groupSlug,
roles: selectedRoles
});

View File

@@ -17,8 +17,6 @@ import { GroupTable } from "./GroupsTable";
export const GroupsSection = () => {
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?.id ?? "";
const { mutateAsync: deleteMutateAsync } = useDeleteGroupFromWorkspace();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
@@ -31,7 +29,7 @@ export const GroupsSection = () => {
try {
await deleteMutateAsync({
groupSlug,
workspaceId
projectSlug: currentWorkspace?.slug || ""
});
createNotification({

View File

@@ -33,7 +33,7 @@ type Props = {
export const GroupTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWorkspaceGroupMemberships(currentWorkspace?.id || "");
const { data, isLoading } = useGetWorkspaceGroupMemberships(currentWorkspace?.slug || "");
return (
<TableContainer>
<Table>