From fb447a1852a95dcacfdb0aa896fd1521430fa095 Mon Sep 17 00:00:00 2001 From: yogesh <114429499+yogesh1801@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:50:01 +0530 Subject: [PATCH] feat(api): Add `requireRestart` parameter (#286) Signed-off-by: yogesh1801 --- .../migration.sql | 5 ++ apps/api/src/prisma/schema.prisma | 28 ++++---- .../secret/dto/create.secret/create.secret.ts | 6 ++ apps/api/src/secret/secret.e2e.spec.ts | 58 ++++++++++++++++ apps/api/src/secret/service/secret.service.ts | 4 ++ apps/api/src/socket/socket.types.ts | 1 + .../dto/create.variable/create.variable.ts | 6 ++ .../src/variable/service/variable.service.ts | 4 ++ apps/api/src/variable/variable.e2e.spec.ts | 66 +++++++++++++++++++ apps/platform/src/lib/workspace-storage.ts | 2 +- 10 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql diff --git a/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql b/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql new file mode 100644 index 00000000..8219de76 --- /dev/null +++ b/apps/api/src/prisma/migrations/20240625190757_add_require_restart_field/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Secret" ADD COLUMN "requireRestart" BOOLEAN DEFAULT false; + +-- AlterTable +ALTER TABLE "Variable" ADD COLUMN "requireRestart" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/api/src/prisma/schema.prisma b/apps/api/src/prisma/schema.prisma index 551bc7f1..2a69aad6 100644 --- a/apps/api/src/prisma/schema.prisma +++ b/apps/api/src/prisma/schema.prisma @@ -358,13 +358,14 @@ model SecretVersion { } model Secret { - id String @id @default(cuid()) - name String - versions SecretVersion[] // Stores the versions of the secret - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - rotateAt DateTime? - note String? + id String @id @default(cuid()) + name String + versions SecretVersion[] // Stores the versions of the secret + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + rotateAt DateTime? + note String? + requireRestart Boolean @default(false) lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull) lastUpdatedById String? @@ -394,12 +395,13 @@ model VariableVersion { } model Variable { - id String @id @default(cuid()) - name String - versions VariableVersion[] // Stores the versions of the variable - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - note String? + id String @id @default(cuid()) + name String + versions VariableVersion[] // Stores the versions of the variable + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + note String? + requireRestart Boolean @default(false) lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull) lastUpdatedById String? diff --git a/apps/api/src/secret/dto/create.secret/create.secret.ts b/apps/api/src/secret/dto/create.secret/create.secret.ts index ba4c1548..4e05dead 100644 --- a/apps/api/src/secret/dto/create.secret/create.secret.ts +++ b/apps/api/src/secret/dto/create.secret/create.secret.ts @@ -4,6 +4,7 @@ import { IsArray, IsOptional, IsString, + IsBoolean, Length, ValidateNested } from 'class-validator' @@ -21,6 +22,11 @@ export class CreateSecret { @IsOptional() rotateAfter?: '24' | '168' | '720' | '8760' | 'never' = 'never' + @IsBoolean() + @IsOptional() + @Transform(({ value }) => value === 'true' || value === true) + requireRestart?: boolean + @IsOptional() @IsArray() @ValidateNested({ each: true }) diff --git a/apps/api/src/secret/secret.e2e.spec.ts b/apps/api/src/secret/secret.e2e.spec.ts index f722c542..1b24dbe9 100644 --- a/apps/api/src/secret/secret.e2e.spec.ts +++ b/apps/api/src/secret/secret.e2e.spec.ts @@ -207,6 +207,40 @@ describe('Secret Controller Tests', () => { expect(body.versions[0].value).not.toBe('Secret 2 value') }) + it('should be able to create a secret with requireRestart set to true', async () => { + const response = await app.inject({ + method: 'POST', + url: `/secret/${project1.id}`, + payload: { + name: 'Secret 3', + note: 'Secret 3 note', + requireRestart: true, + entries: [ + { + value: 'Secret 3 value', + environmentId: environment1.id + } + ], + rotateAfter: '24' + }, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(201) + + const body = response.json() + + expect(body).toBeDefined() + expect(body.name).toBe('Secret 3') + expect(body.note).toBe('Secret 3 note') + expect(body.requireRestart).toBe(true) + expect(body.projectId).toBe(project1.id) + expect(body.versions.length).toBe(1) + expect(body.versions[0].value).not.toBe('Secret 3 value') + }) + it('should have created a secret version', async () => { const secretVersion = await prisma.secretVersion.findFirst({ where: { @@ -343,6 +377,30 @@ describe('Secret Controller Tests', () => { expect(secretVersion.length).toBe(1) }) + it('should be able to update the requireRestart Param without creating a new version', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/secret/${secret1.id}`, + payload: { + requireRestart: true + }, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().requireRestart).toEqual(true) + + const secretVersion = await prisma.secretVersion.findMany({ + where: { + secretId: secret1.id + } + }) + + expect(secretVersion.length).toBe(1) + }) + it('should create a new version if the value is updated', async () => { const response = await app.inject({ method: 'PUT', diff --git a/apps/api/src/secret/service/secret.service.ts b/apps/api/src/secret/service/secret.service.ts index 7a143872..da9336c0 100644 --- a/apps/api/src/secret/service/secret.service.ts +++ b/apps/api/src/secret/service/secret.service.ts @@ -88,6 +88,7 @@ export class SecretService { name: dto.name, note: dto.note, rotateAt: addHoursToDate(dto.rotateAfter), + requireRestart: dto.requireRestart, versions: shouldCreateRevisions && { createMany: { data: await Promise.all( @@ -198,6 +199,7 @@ export class SecretService { data: { name: dto.name, note: dto.note, + requireRestart: dto.requireRestart, rotateAt: dto.rotateAfter ? addHoursToDate(dto.rotateAfter) : undefined, @@ -268,6 +270,7 @@ export class SecretService { environmentId: entry.environmentId, name: updatedSecret.name, value: entry.value, + requireRestart: dto.requireRestart, isSecret: true }) ) @@ -352,6 +355,7 @@ export class SecretService { environmentId, name: secret.name, value: secret.versions[rollbackVersion - 1].value, + requireRestart: secret.requireRestart, isSecret: true }) ) diff --git a/apps/api/src/socket/socket.types.ts b/apps/api/src/socket/socket.types.ts index 92bea27d..029b95ad 100644 --- a/apps/api/src/socket/socket.types.ts +++ b/apps/api/src/socket/socket.types.ts @@ -7,6 +7,7 @@ export interface ChangeNotifierRegistration { export interface ChangeNotification { name: string value: string + requireRestart: boolean isSecret: boolean } diff --git a/apps/api/src/variable/dto/create.variable/create.variable.ts b/apps/api/src/variable/dto/create.variable/create.variable.ts index 20502a4f..a585bc7d 100644 --- a/apps/api/src/variable/dto/create.variable/create.variable.ts +++ b/apps/api/src/variable/dto/create.variable/create.variable.ts @@ -4,6 +4,7 @@ import { IsArray, IsOptional, IsString, + IsBoolean, Length, ValidateNested } from 'class-validator' @@ -19,6 +20,11 @@ export class CreateVariable { @Transform(({ value }) => (value ? value.trim() : null)) note?: string + @IsBoolean() + @IsOptional() + @Transform(({ value }) => value === 'true' || value === true) + requireRestart?: boolean + @IsOptional() @IsArray() @ValidateNested({ each: true }) diff --git a/apps/api/src/variable/service/variable.service.ts b/apps/api/src/variable/service/variable.service.ts index 7c8b7d37..69e6c14c 100644 --- a/apps/api/src/variable/service/variable.service.ts +++ b/apps/api/src/variable/service/variable.service.ts @@ -88,6 +88,7 @@ export class VariableService { data: { name: dto.name, note: dto.note, + requireRestart: dto.requireRestart, versions: shouldCreateRevisions && { createMany: { data: dto.entries.map((entry) => ({ @@ -200,6 +201,7 @@ export class VariableService { data: { name: dto.name, note: dto.note, + requireRestart: dto.requireRestart, lastUpdatedById: user.id }, include: { @@ -266,6 +268,7 @@ export class VariableService { environmentId: entry.environmentId, name: updatedVariable.name, value: entry.value, + requireRestart: dto.requireRestart, isSecret: false }) ) @@ -353,6 +356,7 @@ export class VariableService { environmentId, name: variable.name, value: variable.versions[rollbackVersion - 1].value, + requireRestart: variable.requireRestart, isSecret: false }) ) diff --git a/apps/api/src/variable/variable.e2e.spec.ts b/apps/api/src/variable/variable.e2e.spec.ts index 203ff34b..78376e6d 100644 --- a/apps/api/src/variable/variable.e2e.spec.ts +++ b/apps/api/src/variable/variable.e2e.spec.ts @@ -209,6 +209,48 @@ describe('Variable Controller Tests', () => { expect(variable).toBeDefined() }) + it('should be able to create a variable with requireRestart set to true', async () => { + const response = await app.inject({ + method: 'POST', + url: `/variable/${project1.id}`, + payload: { + name: 'Variable 4', + note: 'Variable 4 note', + requireRestart: true, + rotateAfter: '24', + entries: [ + { + value: 'Variable 4 value', + environmentId: environment2.id + } + ] + }, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(201) + + const body = response.json() + + expect(body).toBeDefined() + expect(body.name).toBe('Variable 4') + expect(body.note).toBe('Variable 4 note') + expect(body.requireRestart).toBe(true) + expect(body.projectId).toBe(project1.id) + expect(body.versions.length).toBe(1) + expect(body.versions[0].value).toBe('Variable 4 value') + + const variable = await prisma.variable.findUnique({ + where: { + id: body.id + } + }) + + expect(variable).toBeDefined() + }) + it('should have created a variable version', async () => { const variableVersion = await prisma.variableVersion.findFirst({ where: { @@ -363,6 +405,30 @@ describe('Variable Controller Tests', () => { expect(variableVersion.length).toBe(1) }) + it('should be able to update requireRestart param without creating a new version', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/variable/${variable1.id}`, + payload: { + requireRestart: true + }, + headers: { + 'x-e2e-user-email': user1.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json().requireRestart).toEqual(true) + + const variableVersion = await prisma.variableVersion.findMany({ + where: { + variableId: variable1.id + } + }) + + expect(variableVersion.length).toBe(1) + }) + it('should create a new version if the value is updated', async () => { const response = await app.inject({ method: 'PUT', diff --git a/apps/platform/src/lib/workspace-storage.ts b/apps/platform/src/lib/workspace-storage.ts index 48f32216..5950765f 100644 --- a/apps/platform/src/lib/workspace-storage.ts +++ b/apps/platform/src/lib/workspace-storage.ts @@ -19,4 +19,4 @@ export function getCurrentWorkspace(): Workspace | null { export function setCurrentWorkspace(workspace: Workspace): void { localStorage.setItem('currentWorkspace', JSON.stringify(workspace)) -} \ No newline at end of file +}