Skip to content

Commit

Permalink
feat(api): Add global search in workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Sep 5, 2024
1 parent 34e6c39 commit c49962b
Show file tree
Hide file tree
Showing 4 changed files with 471 additions and 14 deletions.
28 changes: 17 additions & 11 deletions apps/api/src/common/authority-checker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(permittedAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(permittedAuthorities, authorities, userId)

return workspace
}
Expand Down Expand Up @@ -140,23 +140,29 @@ export class AuthorityCheckerService {
const projectAccessLevel = project.accessLevel
switch (projectAccessLevel) {
case ProjectAccessLevel.GLOBAL:
if (!authorities.includes(Authority.READ_PROJECT)) {
this.checkHasPermission(
// In the global case, we check if the authorities being passed in
// contains just the READ_PROJECT authority. If not, we need to
// check if the user has access to the other authorities mentioned as well.
if (
authorities.length !== 1 ||
!authorities.includes(Authority.READ_PROJECT)
) {
this.checkHasPermissionOverEntity(
permittedAuthoritiesForWorkspace,
authorities,
userId
)
}
break
case ProjectAccessLevel.INTERNAL:
this.checkHasPermission(
this.checkHasPermissionOverEntity(
permittedAuthoritiesForWorkspace,
authorities,
userId
)
break
case ProjectAccessLevel.PRIVATE:
this.checkHasPermission(
this.checkHasPermissionOverEntity(
permittedAuthoritiesForProject,
authorities,
userId
Expand Down Expand Up @@ -219,7 +225,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(permittedAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(permittedAuthorities, authorities, userId)

return environment
}
Expand Down Expand Up @@ -278,7 +284,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(permittedAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(permittedAuthorities, authorities, userId)

return variable
}
Expand Down Expand Up @@ -337,7 +343,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(permittedAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(permittedAuthorities, authorities, userId)

return secret
}
Expand Down Expand Up @@ -388,7 +394,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(permittedAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(permittedAuthorities, authorities, userId)

if (integration.projectId) {
const project = await prisma.project.findUnique({
Expand All @@ -409,7 +415,7 @@ export class AuthorityCheckerService {
prisma
)

this.checkHasPermission(projectAuthorities, authorities, userId)
this.checkHasPermissionOverEntity(projectAuthorities, authorities, userId)
}

return integration
Expand All @@ -425,7 +431,7 @@ export class AuthorityCheckerService {
* @returns void
* @throws UnauthorizedException if the user does not have all the required authorities
*/
private checkHasPermission(
private checkHasPermissionOverEntity(
permittedAuthorities: Set<Authority>,
authorities: Authority[],
userId: string
Expand Down
16 changes: 16 additions & 0 deletions apps/api/src/workspace/controller/workspace.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,20 @@ export class WorkspaceController {
search
)
}

@Get(':workspaceId/global-search/:searchTerm')
@RequiredApiKeyAuthorities(
Authority.READ_WORKSPACE,
Authority.READ_ENVIRONMENT,
Authority.READ_SECRET,
Authority.READ_VARIABLE,
Authority.READ_PROJECT
)
async globalSearch(
@CurrentUser() user: User,
@Param('workspaceId') workspaceId: Workspace['id'],
@Param('searchTerm') searchTerm: string
) {
return this.workspaceService.globalSearch(user, workspaceId, searchTerm)
}
}
151 changes: 151 additions & 0 deletions apps/api/src/workspace/service/workspace.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import {
import { PrismaService } from '../../prisma/prisma.service'
import {
Authority,
Environment,
EventSource,
EventType,
Project,
ProjectAccessLevel,
Secret,
User,
Variable,
Workspace,
WorkspaceMember,
WorkspaceRole
Expand All @@ -31,6 +36,7 @@ import { v4 } from 'uuid'
import createEvent from '../../common/create-event'
import createWorkspace from '../../common/create-workspace'
import { AuthorityCheckerService } from '../../common/authority-checker.service'
import getCollectiveProjectAuthorities from '../../common/get-collective-project-authorities'
import { paginate } from '../../common/paginate'
import { limitMaxItemsPerPage } from '../../common/limit-max-items-per-page'

Expand Down Expand Up @@ -918,6 +924,151 @@ export class WorkspaceService {
return data
}

async globalSearch(
user: User,
workspaceId: string,
searchTerm: string
): Promise<{
projects: Partial<Project>[]
environments: Partial<Environment>[]
secrets: Partial<Secret>[]
variables: Partial<Variable>[]
}> {
// Check authority over workspace
await this.authorityCheckerService.checkAuthorityOverWorkspace({
userId: user.id,
entity: { id: workspaceId },
authorities: [
Authority.READ_WORKSPACE,
Authority.READ_PROJECT,
Authority.READ_ENVIRONMENT,
Authority.READ_SECRET,
Authority.READ_VARIABLE
],
prisma: this.prisma
})

// Get a list of project IDs that the user has access to READ
const accessibleProjectIds = await this.getAccessibleProjectIds(
user.id,
workspaceId
)

// Query all entities based on the search term and permissions
const projects = await this.queryProjects(accessibleProjectIds, searchTerm)
const environments = await this.queryEnvironments(
accessibleProjectIds,
searchTerm
)
const secrets = await this.querySecrets(accessibleProjectIds, searchTerm)
const variables = await this.queryVariables(
accessibleProjectIds,
searchTerm
)

return { projects, environments, secrets, variables }
}
private async getAccessibleProjectIds(
userId: string,
workspaceId: string
): Promise<string[]> {
const projects = await this.prisma.project.findMany({
where: { workspaceId }
})

const accessibleProjectIds: string[] = []
for (const project of projects) {
if (project.accessLevel === ProjectAccessLevel.GLOBAL) {
accessibleProjectIds.push(project.id)
}

const authorities = await getCollectiveProjectAuthorities(
userId,
project,
this.prisma
)
if (
authorities.has(Authority.READ_PROJECT) ||
authorities.has(Authority.WORKSPACE_ADMIN)
) {
accessibleProjectIds.push(project.id)
}
}
return accessibleProjectIds
}

private async queryProjects(
projectIds: string[],
searchTerm: string
): Promise<Partial<Project>[]> {
// Fetch projects where user has READ_PROJECT authority and match search term
return this.prisma.project.findMany({
where: {
id: { in: projectIds },
OR: [
{ name: { contains: searchTerm, mode: 'insensitive' } },
{ description: { contains: searchTerm, mode: 'insensitive' } }
]
},
select: { id: true, name: true, description: true }
})
}

private async queryEnvironments(
projectIds: string[],
searchTerm: string
): Promise<Partial<Environment>[]> {
return this.prisma.environment.findMany({
where: {
project: {
id: { in: projectIds }
},
OR: [
{ name: { contains: searchTerm, mode: 'insensitive' } },
{ description: { contains: searchTerm, mode: 'insensitive' } }
]
},
select: { id: true, name: true, description: true }
})
}

private async querySecrets(
projectIds: string[],
searchTerm: string
): Promise<Partial<Secret>[]> {
// Fetch secrets associated with projects user has READ_SECRET authority on
return await this.prisma.secret.findMany({
where: {
project: {
id: { in: projectIds }
},
OR: [
{ name: { contains: searchTerm, mode: 'insensitive' } },
{ note: { contains: searchTerm, mode: 'insensitive' } }
]
},
select: { id: true, name: true, note: true }
})
}

private async queryVariables(
projectIds: string[],
searchTerm: string
): Promise<Partial<Variable>[]> {
return this.prisma.variable.findMany({
where: {
project: {
id: { in: projectIds }
},
OR: [
{ name: { contains: searchTerm, mode: 'insensitive' } },
{ note: { contains: searchTerm, mode: 'insensitive' } }
]
},
select: { id: true, name: true, note: true }
})
}

private async existsByName(
name: string,
userId: User['id']
Expand Down
Loading

0 comments on commit c49962b

Please sign in to comment.