Skip to content

Commit

Permalink
feat(api): Add endpoint to fetch all workspace invitations for a user (
Browse files Browse the repository at this point in the history
…#586)

Co-authored-by: Rajdip Bhattacharya <[email protected]>
  • Loading branch information
muntaxir4 and rajdip-b authored Dec 13, 2024
1 parent a33f4b5 commit d45417a
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
meta {
name: Get all invitations of user to workspaces
type: http
seq: 3
}

get {
url: {{BASE_URL}}/api/workspace/invitations?page=0&limit=10
body: none
auth: bearer
}

auth:bearer {
token: {{JWT}}
}

docs {
## Description

Fetches all the workspaces where the user is invited to.
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator'
export class CreateEnvironment {
@IsString()
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9-_]{1,64}$/)
name: string

@IsString()
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/environment/environment.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { UserModule } from '@/user/user.module'
import { UserService } from '@/user/service/user.service'
import { QueryTransformPipe } from '@/common/pipes/query.transform.pipe'
import { fetchEvents } from '@/common/event'
import { ValidationPipe } from '@nestjs/common'

describe('Environment Controller Tests', () => {
let app: NestFastifyApplication
Expand Down Expand Up @@ -65,7 +66,7 @@ describe('Environment Controller Tests', () => {
environmentService = moduleRef.get(EnvironmentService)
userService = moduleRef.get(UserService)

app.useGlobalPipes(new QueryTransformPipe())
app.useGlobalPipes(new ValidationPipe(), new QueryTransformPipe())

await app.init()
await app.getHttpAdapter().getInstance().ready()
Expand Down Expand Up @@ -184,7 +185,7 @@ describe('Environment Controller Tests', () => {
'x-e2e-user-email': user1.email
}
})

expect(response.statusCode).toBe(400)
expect(response.json().message).toContain('name should not be empty')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "WorkspaceMember" ADD COLUMN "createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
1 change: 1 addition & 0 deletions apps/api/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ model WorkspaceMember {
workspaceId String
invitationAccepted Boolean @default(false)
roles WorkspaceMemberRoleAssociation[]
createdOn DateTime @default(now())
@@unique([workspaceId, userId])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -879,11 +879,14 @@ export class WorkspaceMembershipService {
roleSet.add(role)
}

const invitedOn = new Date()

// Create the workspace membership
const createMembership = this.prisma.workspaceMember.create({
data: {
workspaceId: workspace.id,
userId,
createdOn: invitedOn,
roles: {
create: Array.from(roleSet).map((role) => ({
role: {
Expand All @@ -904,7 +907,7 @@ export class WorkspaceMembershipService {
workspace.name,
`${process.env.WORKSPACE_FRONTEND_URL}/workspace/${workspace.slug}/join`,
currentUser.name,
new Date().toISOString(),
invitedOn.toISOString(),
true
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ describe('Workspace Membership Controller Tests', () => {
id: expect.any(String),
userId: user2.id,
workspaceId: workspace1.id,
invitationAccepted: false
invitationAccepted: false,
createdOn: expect.any(Date)
})
})

Expand Down Expand Up @@ -909,7 +910,8 @@ describe('Workspace Membership Controller Tests', () => {
id: expect.any(String),
userId: user2.id,
workspaceId: workspace1.id,
invitationAccepted: true
invitationAccepted: true,
createdOn: expect.any(Date)
})
})

Expand Down
20 changes: 20 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,26 @@ 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 = ''
) {
return this.workspaceService.getAllWorkspaceInvitations(
user,
page,
limit,
sort,
order,
search
)
}

@Get(':workspaceSlug')
@RequiredApiKeyAuthorities(Authority.READ_WORKSPACE)
async getWorkspace(
Expand Down
105 changes: 105 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,111 @@ export class WorkspaceService {
return { projects, environments, secrets, variables }
}

/**
* Gets all the invitations a user has to various workspaces, paginated.
* @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
* @returns The workspace invitations of the user, paginated, with metadata
*/
async getAllWorkspaceInvitations(
user: User,
page: number,
limit: number,
sort: string,
order: string,
search: string
) {
// 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: false,
workspace: {
name: {
contains: search
}
},
roles: {
none: {
role: {
hasAdminAuthority: true
}
}
}
},
select: {
workspace: {
select: {
id: true,
name: true,
slug: true,
icon: true
}
},
roles: {
select: {
role: {
select: {
name: true,
colorCode: true
}
}
}
},
createdOn: true
}
})

// get total count of workspaces of the user
const totalCount = await this.prisma.workspaceMember.count({
where: {
userId: user.id,
invitationAccepted: false,
workspace: {
name: {
contains: search
}
},
roles: {
none: {
role: {
hasAdminAuthority: true
}
}
}
}
})

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

return {
items: items.map((item) => ({
...item,
invitedOn: item.createdOn,
createdOn: undefined
})),
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
Loading

0 comments on commit d45417a

Please sign in to comment.