Skip to content

Commit

Permalink
feat(platform): Operate on environments (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b authored Jan 28, 2025
1 parent 4a35fe7 commit f45c5fa
Show file tree
Hide file tree
Showing 27 changed files with 1,104 additions and 65 deletions.
106 changes: 105 additions & 1 deletion apps/api/src/environment/environment.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ 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'
import { SecretService } from '@/secret/service/secret.service'
import { VariableService } from '@/variable/service/variable.service'
import { SecretModule } from '@/secret/secret.module'
import { VariableModule } from '@/variable/variable.module'

describe('Environment Controller Tests', () => {
let app: NestFastifyApplication
Expand All @@ -37,6 +41,8 @@ describe('Environment Controller Tests', () => {
let environmentService: EnvironmentService
let userService: UserService
let eventService: EventService
let secretService: SecretService
let variableService: VariableService

let user1: User, user2: User
let workspace1: Workspace
Expand All @@ -50,7 +56,9 @@ describe('Environment Controller Tests', () => {
EventModule,
ProjectModule,
EnvironmentModule,
UserModule
UserModule,
SecretModule,
VariableModule
]
})
.overrideProvider(MAIL_SERVICE)
Expand All @@ -65,6 +73,8 @@ describe('Environment Controller Tests', () => {
eventService = moduleRef.get(EventService)
environmentService = moduleRef.get(EnvironmentService)
userService = moduleRef.get(UserService)
secretService = moduleRef.get(SecretService)
variableService = moduleRef.get(VariableService)

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

Expand Down Expand Up @@ -465,6 +475,100 @@ describe('Environment Controller Tests', () => {
)
})

it('should be able to get the count of secrets and variables in an environment', async () => {
// Add secrets to the environment
const secret1 = await secretService.createSecret(
user1,
{
name: 'Secret 1',
entries: [
{
value: 'Secret 1 value',
environmentSlug: environment1.slug
}
]
},
project1.slug
)
await secretService.createSecret(
user1,
{
name: 'Secret 2',
entries: [
{
value: 'Secret 2 value',
environmentSlug: environment1.slug
}
]
},
project1.slug
)

// Add variables to the environment
const variable1 = await variableService.createVariable(
user1,
{
name: 'Variable 1',
entries: [
{
value: 'Variable 1 value',
environmentSlug: environment1.slug
}
]
},
project1.slug
)
await variableService.createVariable(
user1,
{
name: 'Variable 2',
entries: [
{
value: 'Variable 2 value',
environmentSlug: environment1.slug
}
]
},
project1.slug
)

// Update the value of a secret to add a SecretVersion
await secretService.updateSecret(user1, secret1.secret.slug, {
entries: [
{
value: 'Updated Secret 1 value',
environmentSlug: environment1.slug
}
]
})

// Update the value of a variable to add a VariableVersion
await variableService.updateVariable(user1, variable1.variable.slug, {
entries: [
{
value: 'Updated Variable 1 value',
environmentSlug: environment1.slug
}
]
})

const response = await app.inject({
method: 'GET',
url: `/environment/all/${project1.slug}?page=0&limit=10`,
headers: {
'x-e2e-user-email': user1.email
}
})

expect(response.statusCode).toBe(200)
const devEnvironment = response
.json()
.items.find((env: Environment) => env.slug === environment1.slug)

expect(devEnvironment.secrets).toBe(2)
expect(devEnvironment.variables).toBe(2)
})

it('should not be able to fetch all environments of a project that does not exist', async () => {
const response = await app.inject({
method: 'GET',
Expand Down
59 changes: 59 additions & 0 deletions apps/api/src/environment/service/environment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ export class EnvironmentService {
id: user.id
}
}
},
include: {
lastUpdatedBy: {
select: {
id: true,
name: true,
profilePictureUrl: true,
email: true
}
}
}
})

Expand Down Expand Up @@ -312,6 +322,17 @@ export class EnvironmentService {
[sort]: order
}
})

// Parse the secret and variable counts for each environment
for (const environment of items) {
const [secrets, variables] = await Promise.all([
this.getSecretCount(environment.id),
this.getVariableCount(environment.id)
])
environment['secrets'] = secrets
environment['variables'] = variables
}

// Calculate metadata for pagination
const totalCount = await this.prisma.environment.count({
where: {
Expand Down Expand Up @@ -422,4 +443,42 @@ export class EnvironmentService {
)
}
}

/**
* Counts the number of unique secrets in an environment.
* @param environmentId The ID of the environment to count secrets for.
* @returns The number of unique secrets in the environment.
* @private
*/
private async getSecretCount(
environmentId: Environment['id']
): Promise<number> {
const secrets = await this.prisma.secretVersion.findMany({
distinct: ['secretId'],
where: {
environmentId
}
})

return secrets.length
}

/**
* Counts the number of unique variables in an environment.
* @param environmentId The ID of the environment to count variables for.
* @returns The number of unique variables in the environment.
* @private
*/
private async getVariableCount(
environmentId: Environment['id']
): Promise<number> {
const variables = await this.prisma.variableVersion.findMany({
distinct: ['variableId'],
where: {
environmentId
}
})

return variables.length
}
}
46 changes: 45 additions & 1 deletion apps/api/src/workspace/service/workspace.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class WorkspaceService {
order: string,
search: string
) {
//get all workspaces of user for page with limit
// Get all workspaces of user for page with limit
const items = await this.prisma.workspace.findMany({
skip: page * limit,
take: Number(limit),
Expand All @@ -223,6 +223,13 @@ export class WorkspaceService {
}
})

for (const workspace of items) {
workspace['projects'] = await this.getProjectsOfWorkspace(
workspace.id,
user.id
)
}

// get total count of workspaces of the user
const totalCount = await this.prisma.workspace.count({
where: {
Expand Down Expand Up @@ -659,4 +666,41 @@ export class WorkspaceService {
})) > 0
)
}

/**
* Retrieves the count of projects within a workspace that a user has permission to access.
*
* @param workspaceId The ID of the workspace to retrieve projects from.
* @param userId The ID of the user whose access permissions are being checked.
* @returns The number of projects the user has authority to access within the specified workspace.
* @private
*/

private async getProjectsOfWorkspace(
workspaceId: Workspace['id'],
userId: User['id']
) {
const projects = await this.prisma.project.findMany({
where: {
workspaceId
}
})

let accessibleProjectCount = 0

for (const project of projects) {
const hasAuthority =
await this.authorityCheckerService.checkAuthorityOverProject({
userId,
entity: { slug: project.slug },
authorities: [Authority.READ_PROJECT],
prisma: this.prisma
})

if (hasAuthority) {
accessibleProjectCount++
}
}
return accessibleProjectCount
}
}
24 changes: 24 additions & 0 deletions apps/api/src/workspace/workspace.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,30 @@ describe('Workspace Controller Tests', () => {
)
})

it('should be able to fetch the number of projects accessible by the user', async () => {
// Create a project accessible to the user
await projectService.createProject(user1, workspace1.slug, {
name: 'Project 1',
description: 'Description 1',
accessLevel: ProjectAccessLevel.GLOBAL
})

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

expect(response.statusCode).toBe(200)
const workspaceOfProject = response
.json()
.items.find((workspace: any) => workspace.slug === workspace1.slug)

expect(workspaceOfProject.projects).toBe(1)
})

it('should be able to fetch the 2nd page of the workspaces the user is a member of', async () => {
await createMembership(memberRole.id, user2.id, workspace1.id, prisma)
const response = await app.inject({
Expand Down
9 changes: 0 additions & 9 deletions apps/platform/public/svg/dashboard/config.svg

This file was deleted.

2 changes: 1 addition & 1 deletion apps/platform/public/svg/dashboard/environment.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/platform/public/svg/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import EnvironmentSVG from './environment.svg'
import ConfigSVG from './config.svg'
import SecretSVG from './secret.svg'
import FolderSVG from './folder.svg'
import VariableSVG from './variable.svg'

export { EnvironmentSVG, ConfigSVG, SecretSVG, FolderSVG }
export { EnvironmentSVG, SecretSVG, FolderSVG, VariableSVG }
2 changes: 1 addition & 1 deletion apps/platform/public/svg/dashboard/secret.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions apps/platform/public/svg/dashboard/variable.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f45c5fa

Please sign in to comment.