Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@
"better-auth": "^1.4.22",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"docx": "^9.7.1",
"dotenv": "^17.2.3",
"esbuild": "^0.27.1",
"exceljs": "^4.4.0",
"express": "^4.21.2",
"helmet": "^8.1.0",
"jose": "^6.0.12",
"jspdf": "^4.2.0",
"jspdf-autotable": "^5.0.8",
"mammoth": "^1.8.0",
"nanoid": "^5.1.6",
"pdf-lib": "^1.17.1",
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { VendorsModule } from './vendors/vendors.module';
import { ContextModule } from './context/context.module';
import { TrustPortalModule } from './trust-portal/trust-portal.module';
import { ControlTemplateModule } from './framework-editor/control-template/control-template.module';
import { IsmsDocumentTemplateModule } from './framework-editor/isms-document-template/isms-document-template.module';
import { FrameworkEditorFrameworkModule } from './framework-editor/framework/framework.module';
import { PolicyTemplateModule } from './framework-editor/policy-template/policy-template.module';
import { RequirementModule } from './framework-editor/requirement/requirement.module';
Expand All @@ -34,6 +35,7 @@ import { QuestionnaireModule } from './questionnaire/questionnaire.module';
import { VectorStoreModule } from './vector-store/vector-store.module';
import { KnowledgeBaseModule } from './knowledge-base/knowledge-base.module';
import { SOAModule } from './soa/soa.module';
import { IsmsModule } from './isms/isms.module';
import { IntegrationPlatformModule } from './integration-platform/integration-platform.module';
import { CloudSecurityModule } from './cloud-security/cloud-security.module';
import { BrowserbaseModule } from './browserbase/browserbase.module';
Expand Down Expand Up @@ -95,6 +97,7 @@ import { OffboardingChecklistModule } from './offboarding-checklist/offboarding-
HealthModule,
TrustPortalModule,
ControlTemplateModule,
IsmsDocumentTemplateModule,
FrameworkEditorFrameworkModule,
PolicyTemplateModule,
RequirementModule,
Expand All @@ -105,6 +108,7 @@ import { OffboardingChecklistModule } from './offboarding-checklist/offboarding-
VectorStoreModule,
KnowledgeBaseModule,
SOAModule,
IsmsModule,
IntegrationPlatformModule,
CloudSecurityModule,
BrowserbaseModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';

export class UpdateIsmsDocumentTemplateDto {
@ApiPropertyOptional({ example: 'Context of the Organization' })
@IsString()
@IsOptional()
@MaxLength(255)
name?: string;

@ApiPropertyOptional({
example: 'Internal and external issues relevant to the ISMS.',
})
@IsString()
@IsOptional()
@MaxLength(5000)
description?: string;

@ApiPropertyOptional({ example: '4.1' })
@IsString()
@IsOptional()
@MaxLength(32)
clause?: string;

@ApiPropertyOptional({ example: 0 })
@IsInt()
@IsOptional()
@Min(0)
sortOrder?: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
jest.mock('@db', () => ({ db: {} }));

import { Test, TestingModule } from '@nestjs/testing';
import { PlatformAdminGuard } from '../../auth/platform-admin.guard';
import { IsmsDocumentTemplateController } from './isms-document-template.controller';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

jest.mock('../../auth/platform-admin.guard', () => ({
PlatformAdminGuard: class MockPlatformAdminGuard {},
}));

describe('IsmsDocumentTemplateController', () => {
let controller: IsmsDocumentTemplateController;

const mockService = {
findAll: jest.fn(),
update: jest.fn(),
linkRequirement: jest.fn(),
unlinkRequirement: jest.fn(),
linkControlTemplate: jest.fn(),
unlinkControlTemplate: jest.fn(),
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [IsmsDocumentTemplateController],
providers: [
{ provide: IsmsDocumentTemplateService, useValue: mockService },
],
})
.overrideGuard(PlatformAdminGuard)
.useValue({ canActivate: () => true })
.compile();

controller = module.get(IsmsDocumentTemplateController);
jest.clearAllMocks();
});

it('passes the frameworkId filter to findAll', async () => {
mockService.findAll.mockResolvedValue([]);

await controller.findAll('fw_1');

expect(mockService.findAll).toHaveBeenCalledWith('fw_1');
});

it('passes id and dto to update', async () => {
mockService.update.mockResolvedValue({ id: 'tpl_ctx' });

await controller.update('tpl_ctx', { name: 'New' });

expect(mockService.update).toHaveBeenCalledWith('tpl_ctx', { name: 'New' });
});

it('maps params + query to linkRequirement', async () => {
mockService.linkRequirement.mockResolvedValue({ message: 'linked' });

await controller.linkRequirement('tpl_ctx', 'req_41', 'fw_1');

expect(mockService.linkRequirement).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
requirementId: 'req_41',
frameworkId: 'fw_1',
});
});

it('maps params + query to unlinkRequirement', async () => {
mockService.unlinkRequirement.mockResolvedValue({ message: 'unlinked' });

await controller.unlinkRequirement('tpl_ctx', 'req_41', 'fw_1');

expect(mockService.unlinkRequirement).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
requirementId: 'req_41',
frameworkId: 'fw_1',
});
});

it('maps params + query to linkControlTemplate', async () => {
mockService.linkControlTemplate.mockResolvedValue({ message: 'linked' });

await controller.linkControlTemplate('tpl_ctx', 'ct_1', 'fw_1');

expect(mockService.linkControlTemplate).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
controlTemplateId: 'ct_1',
frameworkId: 'fw_1',
});
});

it('maps params + query to unlinkControlTemplate', async () => {
mockService.unlinkControlTemplate.mockResolvedValue({ message: 'unlinked' });

await controller.unlinkControlTemplate('tpl_ctx', 'ct_1', 'fw_1');

expect(mockService.unlinkControlTemplate).toHaveBeenCalledWith({
templateId: 'tpl_ctx',
controlTemplateId: 'ct_1',
frameworkId: 'fw_1',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { PlatformAdminGuard } from '../../auth/platform-admin.guard';
import { UpdateIsmsDocumentTemplateDto } from './dto/update-isms-document-template.dto';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

@ApiTags('Framework Editor ISMS Document Templates')
@Controller({ path: 'framework-editor/isms-document-template', version: '1' })
@UseGuards(PlatformAdminGuard)
export class IsmsDocumentTemplateController {
constructor(private readonly service: IsmsDocumentTemplateService) {}

@Get()
@ApiOperation({ summary: 'List ISMS document templates' })
async findAll(@Query('frameworkId') frameworkId?: string) {
return this.service.findAll(frameworkId);
}

@Patch(':id')
@ApiOperation({ summary: 'Update an ISMS document template' })
@UsePipes(new ValidationPipe({ whitelist: true, transform: true }))
async update(
@Param('id') id: string,
@Body() dto: UpdateIsmsDocumentTemplateDto,
) {
return this.service.update(id, dto);
}

@Post(':id/requirements/:requirementId')
@ApiOperation({
summary: 'Link a requirement to an ISMS document template for a framework',
})
async linkRequirement(
@Param('id') id: string,
@Param('requirementId') requirementId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.linkRequirement({
templateId: id,
requirementId,
frameworkId,
});
}

@Delete(':id/requirements/:requirementId')
@ApiOperation({
summary:
'Unlink a requirement from an ISMS document template for a framework',
})
async unlinkRequirement(
@Param('id') id: string,
@Param('requirementId') requirementId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.unlinkRequirement({
templateId: id,
requirementId,
frameworkId,
});
}

@Post(':id/controls/:controlTemplateId')
@ApiOperation({
summary:
'Link a control template to an ISMS document template for a framework',
})
async linkControlTemplate(
@Param('id') id: string,
@Param('controlTemplateId') controlTemplateId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.linkControlTemplate({
templateId: id,
controlTemplateId,
frameworkId,
});
}

@Delete(':id/controls/:controlTemplateId')
@ApiOperation({
summary:
'Unlink a control template from an ISMS document template for a framework',
})
async unlinkControlTemplate(
@Param('id') id: string,
@Param('controlTemplateId') controlTemplateId: string,
@Query('frameworkId') frameworkId?: string,
) {
return this.service.unlinkControlTemplate({
templateId: id,
controlTemplateId,
frameworkId,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthModule } from '../../auth/auth.module';
import { IsmsDocumentTemplateController } from './isms-document-template.controller';
import { IsmsDocumentTemplateService } from './isms-document-template.service';

@Module({
imports: [AuthModule],
controllers: [IsmsDocumentTemplateController],
providers: [IsmsDocumentTemplateService],
exports: [IsmsDocumentTemplateService],
})
export class IsmsDocumentTemplateModule {}
Loading
Loading