Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Add endpoint to fetch all workspace invitations for a user #586

Merged
22 changes: 22 additions & 0 deletions apps/api/src/workspace/controller/workspace.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ export class WorkspaceController {
return this.workspaceService.deleteWorkspace(user, workspaceSlug)
}

@Get('invitations')
@RequiredApiKeyAuthorities(Authority.READ_WORKSPACE)
async getAllInvitationsOfUser(
@CurrentUser() user: User,
@Query('page') page: number = 0,
@Query('limit') limit: number = 10,
@Query('sort') sort: string = 'name',
@Query('order') order: string = 'asc',
@Query('search') search: string = '',
@Query('isAccepted') isAccepted: 'true' | 'false' | undefined = undefined
) {
return this.workspaceService.getAllWorkspaceInvitations(
user,
page,
limit,
sort,
order,
search,
isAccepted ? isAccepted === 'true' : undefined
)
}

@Get(':workspaceSlug')
@RequiredApiKeyAuthorities(Authority.READ_WORKSPACE)
async getWorkspace(
Expand Down
97 changes: 97 additions & 0 deletions apps/api/src/workspace/service/workspace.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,103 @@ export class WorkspaceService {
return { projects, environments, secrets, variables }
}

/**
* Gets all workspaces of a user, paginated.
muntaxir4 marked this conversation as resolved.
Show resolved Hide resolved
* @param user The user to get the workspaces for
* @param page The page number to get
* @param limit The number of items per page to get
* @param sort The field to sort by
* @param order The order to sort in
* @param search The search string to filter by
* @param isAccepted The invitation status to filter by
* @returns The workspace invitations of the user, paginated, with metadata
*/
async getAllWorkspaceInvitations(
user: User,
page: number,
limit: number,
sort: string,
order: string,
search: string,
isAccepted: boolean | undefined
muntaxir4 marked this conversation as resolved.
Show resolved Hide resolved
) {
// fetch all workspaces of user where they are not admin
const items = await this.prisma.workspaceMember.findMany({
skip: page * limit,
take: limitMaxItemsPerPage(Number(limit)),
orderBy: {
workspace: {
[sort]: order
}
},
where: {
userId: user.id,
invitationAccepted: isAccepted,
muntaxir4 marked this conversation as resolved.
Show resolved Hide resolved
roles: {
rajdip-b marked this conversation as resolved.
Show resolved Hide resolved
none: {
role: {
authorities: {
has: Authority.WORKSPACE_ADMIN
}
}
}
}
},
muntaxir4 marked this conversation as resolved.
Show resolved Hide resolved
select: {
workspace: {
select: {
id: true,
name: true,
slug: true,
icon: true
}
},
roles: {
select: {
role: {
select: {
name: true
muntaxir4 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
})

// get total count of workspaces of the user
const totalCount = await this.prisma.workspaceMember.count({
where: {
userId: user.id,
invitationAccepted: isAccepted,
roles: {
none: {
role: {
authorities: {
has: Authority.WORKSPACE_ADMIN
}
}
}
}
}
})

//calculate metadata for pagination
const metadata = paginate(
totalCount,
`/workspace/invitations`,
{
page,
limit: limitMaxItemsPerPage(limit),
sort,
order,
search
},
isAccepted !== undefined ? { isAccepted } : undefined
)

return { items, metadata }
}

/**
* Gets a list of project IDs that the user has access to READ.
* The user has access to a project if the project is global or if the user has the READ_PROJECT authority.
Expand Down
75 changes: 74 additions & 1 deletion apps/api/src/workspace/workspace.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { SecretService } from '@/secret/service/secret.service'
import { VariableService } from '@/variable/service/variable.service'
import { WorkspaceRoleService } from '@/workspace-role/service/workspace-role.service'
import { WorkspaceRoleModule } from '@/workspace-role/workspace-role.module'
import { WorkspaceMembershipService } from '@/workspace-membership/service/workspace-membership.service'
import { WorkspaceMembershipModule } from '@/workspace-membership/workspace-membership.module'
import { fetchEvents } from '@/common/event'

const createMembership = async (
Expand Down Expand Up @@ -71,6 +73,7 @@ describe('Workspace Controller Tests', () => {
let secretService: SecretService
let variableService: VariableService
let workspaceRoleService: WorkspaceRoleService
let workspaceMembershipService: WorkspaceMembershipService

let user1: User, user2: User
let workspace1: Workspace, workspace2: Workspace
Expand All @@ -87,7 +90,8 @@ describe('Workspace Controller Tests', () => {
EnvironmentModule,
SecretModule,
VariableModule,
WorkspaceRoleModule
WorkspaceRoleModule,
WorkspaceMembershipModule
]
})
.overrideProvider(MAIL_SERVICE)
Expand All @@ -106,6 +110,7 @@ describe('Workspace Controller Tests', () => {
secretService = moduleRef.get(SecretService)
variableService = moduleRef.get(VariableService)
workspaceRoleService = moduleRef.get(WorkspaceRoleService)
workspaceMembershipService = moduleRef.get(WorkspaceMembershipService)

app.useGlobalPipes(new QueryTransformPipe())

Expand Down Expand Up @@ -179,6 +184,7 @@ describe('Workspace Controller Tests', () => {
expect(secretService).toBeDefined()
expect(variableService).toBeDefined()
expect(workspaceRoleService).toBeDefined()
expect(workspaceMembershipService).toBeDefined()
})

describe('Create Workspace Tests', () => {
Expand Down Expand Up @@ -481,6 +487,73 @@ describe('Workspace Controller Tests', () => {
})
})

describe('Get All Workspace Invitations Tests', () => {
it('should be able to fetch all the workspace invitations of the user', async () => {
//invite user2 to workspace1
await createMembership(memberRole.id, user2.id, workspace1.id, prisma)

const response = await app.inject({
method: 'GET',
headers: {
'x-e2e-user-email': user2.email
},
url: `/workspace/invitations`
})

const body = response.json()

expect(body.items).toHaveLength(1)
expect(body.items[0].workspace.id).toBe(workspace1.id)
expect(body.items[0].workspace.slug).not.toBe(workspace2.slug)
expect(body.metadata.totalCount).toBe(1)
expect(body.metadata.links.self).toEqual(
`/workspace/invitations?page=0&limit=10&sort=name&order=asc&search=`
)
expect(body.metadata.links.first).toEqual(
`/workspace/invitations?page=0&limit=10&sort=name&order=asc&search=`
)
expect(body.metadata.links.previous).toBeNull()
expect(body.metadata.links.next).toBeNull()
expect(body.metadata.links.last).toEqual(
`/workspace/invitations?page=0&limit=10&sort=name&order=asc&search=`
)
})

it('should be able to fetch all the workspace invitations of the user that are accepted', async () => {
//invite user2 to workspace1
await createMembership(memberRole.id, user2.id, workspace1.id, prisma)

// accept the invitation for user2 to workspace1
await workspaceMembershipService.acceptInvitation(user2, workspace1.slug)

const response = await app.inject({
method: 'GET',
headers: {
'x-e2e-user-email': user2.email
},
url: `/workspace/invitations?isAccepted=true`
})

const body = response.json()

expect(body.items).toHaveLength(1)
expect(body.items[0].workspace.id).toBe(workspace1.id)
expect(body.items[0].workspace.slug).not.toBe(workspace2.slug)
expect(body.metadata.totalCount).toBe(1)
expect(body.metadata.links.self).toEqual(
`/workspace/invitations?isAccepted=true&page=0&limit=10&sort=name&order=asc&search=`
)
expect(body.metadata.links.first).toEqual(
`/workspace/invitations?isAccepted=true&page=0&limit=10&sort=name&order=asc&search=`
)
expect(body.metadata.links.previous).toBeNull()
expect(body.metadata.links.next).toBeNull()
expect(body.metadata.links.last).toEqual(
`/workspace/invitations?isAccepted=true&page=0&limit=10&sort=name&order=asc&search=`
)
})
})

describe('Export Data Tests', () => {
it('should not be able to export data of a non-existing workspace', async () => {
const response = await app.inject({
Expand Down
Loading