diff --git a/apps/api/src/project/project.e2e.spec.ts b/apps/api/src/project/project.e2e.spec.ts index 8aa49e30..a27ab523 100644 --- a/apps/api/src/project/project.e2e.spec.ts +++ b/apps/api/src/project/project.e2e.spec.ts @@ -57,7 +57,7 @@ describe('Project Controller Tests', () => { let user1: User, user2: User let workspace1: Workspace, workspace2: Workspace - let project1: Project, project2: Project, project3: Project + let project1: Project, project2: Project, project3: Project, project4: Project beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -142,6 +142,13 @@ describe('Project Controller Tests', () => { storePrivateKey: true, accessLevel: ProjectAccessLevel.GLOBAL })) as Project + + project4 = (await projectService.createProject(user2, workspace2.slug, { + name: 'Project4', + description: + 'Project for testing if all environments,secrets and keys are being fetched or not', + storePrivateKey: true + })) as Project }) afterEach(async () => { @@ -519,6 +526,97 @@ describe('Project Controller Tests', () => { expect(response.statusCode).toBe(401) }) + + it('should fetch correct counts of environments, variables, and secrets for projects in a workspace', async () => { + // Add an environment to the project + const environment = (await environmentService.createEnvironment( + user2, + { + name: 'Dev' + }, + project4.slug + )) as Environment + + // Add two secrets + ;(await secretService.createSecret( + user2, + { + name: 'API_KEY', + entries: [ + { + value: 'some_key', + environmentSlug: environment.slug + } + ] + }, + project4.slug + )) as Secret + ;(await secretService.createSecret( + user2, + { + name: 'DB_PASSWORD', + entries: [ + { + value: 'password', + environmentSlug: environment.slug + } + ] + }, + project4.slug + )) as Secret + + // Add two variables + ;(await variableService.createVariable( + user2, + { + name: 'PORT', + entries: [ + { + value: '8080', + environmentSlug: environment.slug + } + ] + }, + project4.slug + )) as Variable + ;(await variableService.createVariable( + user2, + { + name: 'EXPIRY', + entries: [ + { + value: '3600', + environmentSlug: environment.slug + } + ] + }, + project4.slug + )) as Variable + + const response = await app.inject({ + method: 'GET', + url: `/project/all/${workspace2.slug}?page=0&limit=10&search=Project4`, + headers: { + 'x-e2e-user-email': user2.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().items.length).toEqual(1) + + const project = response.json().items[0] + expect(project.totalEnvironmentsOfProject).toEqual(2) + expect(project.totalVariablesOfProject).toEqual(2) + expect(project.totalSecretsOfProject).toEqual(2) + // Verify project details + expect(project.name).toEqual('Project4') + expect(project.description).toEqual( + 'Project for testing if all environments,secrets and keys are being fetched or not' + ) + // Verify that sensitive data is not included + expect(project).not.toHaveProperty('privateKey') + expect(project).not.toHaveProperty('publicKey') + }) }) it('should create environments if provided', async () => { diff --git a/apps/api/src/project/service/project.service.ts b/apps/api/src/project/service/project.service.ts index 421d2832..78acca95 100644 --- a/apps/api/src/project/service/project.service.ts +++ b/apps/api/src/project/service/project.service.ts @@ -777,7 +777,7 @@ export class ProjectService { const workspaceId = workspace.id //fetch projects with required properties - const items = ( + const projects = ( await this.prisma.project.findMany({ skip: page * limit, take: limitMaxItemsPerPage(limit), @@ -801,7 +801,19 @@ export class ProjectService { workspace: { members: { some: { - userId: user.id + userId: user.id, + roles: { + some: { + role: { + authorities: { + hasSome: [ + Authority.WORKSPACE_ADMIN, + Authority.READ_PROJECT + ] + } + } + } + } } } } @@ -809,6 +821,76 @@ export class ProjectService { }) ).map((project) => excludeFields(project, 'privateKey', 'publicKey')) + const items = await Promise.all( + projects.map(async (project) => { + let totalEnvironmentsOfProject = 0 + let totalVariablesOfProject = 0 + let totalSecretsOfProject = 0 + // When we later implement RBAC for environments, we would need to updated + // this code to only include environments like we do while fetching projects. + + // What would be even better is, we should fetch environments directly. And then, + // accumulate the projects into a set of projects. And then, return that set along + // with the required data. + const allEnvs = await this.prisma.environment.findMany({ + where: { projectId: project.id } + }) + + // This entire block will become invalid after RBAC for environments are implemented + const envPromises = allEnvs.map(async (env) => { + const hasRequiredPermission = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { slug: env.slug }, + authorities: [ + Authority.READ_ENVIRONMENT, + Authority.READ_SECRET, + Authority.READ_VARIABLE + ], + prisma: this.prisma + }) + if (hasRequiredPermission) { + totalEnvironmentsOfProject += 1 + + const fetchSecretCount = this.prisma.secret.count({ + where: { + projectId: project.id, + versions: { some: { environmentId: env.id } } + } + }) + + const fetchVariableCount = this.prisma.variable.count({ + where: { + projectId: project.id, + versions: { some: { environmentId: env.id } } + } + }) + + return this.prisma.$transaction([ + fetchSecretCount, + fetchVariableCount + ]) + } + return [0, 0] + }) + const counts = await Promise.all(envPromises) + totalSecretsOfProject = counts.reduce( + (sum, [secretCount]) => sum + secretCount, + 0 + ) + totalVariablesOfProject = counts.reduce( + (sum, [, variableCount]) => sum + variableCount, + 0 + ) + return { + ...project, + totalEnvironmentsOfProject, + totalVariablesOfProject, + totalSecretsOfProject + } + }) + ) + //calculate metadata const totalCount = await this.prisma.project.count({ where: {