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/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { CloudSecurityModule } from './cloud-security/cloud-security.module';
import { BrowserbaseModule } from './browserbase/browserbase.module';
import { TaskManagementModule } from './task-management/task-management.module';
import { AssistantChatModule } from './assistant-chat/assistant-chat.module';
import { OrgChartModule } from './org-chart/org-chart.module';
import { TrainingModule } from './training/training.module';

@Module({
Expand Down Expand Up @@ -80,6 +81,7 @@ import { TrainingModule } from './training/training.module';
TaskManagementModule,
AssistantChatModule,
TrainingModule,
OrgChartModule,
],
controllers: [AppController],
providers: [
Expand Down
22 changes: 22 additions & 0 deletions apps/api/src/org-chart/dto/upload-org-chart.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IsNotEmpty, IsString, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class UploadOrgChartDto {
@ApiProperty({ description: 'Original file name' })
@IsString()
@IsNotEmpty()
fileName: string;

@ApiProperty({ description: 'MIME type of the file (e.g. image/png)' })
@IsString()
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9\-]+\/[a-zA-Z0-9\-\+\.]+$/, {
message: 'Invalid MIME type format',
})
fileType: string;

@ApiProperty({ description: 'Base64-encoded file data' })
@IsString()
@IsNotEmpty()
fileData: string;
}
17 changes: 17 additions & 0 deletions apps/api/src/org-chart/dto/upsert-org-chart.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IsArray, IsOptional, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class UpsertOrgChartDto {
@ApiPropertyOptional({ description: 'Name of the org chart' })
@IsOptional()
@IsString()
name?: string;

@ApiProperty({ description: 'React Flow nodes array', type: [Object] })
@IsArray()
nodes: any[];

@ApiProperty({ description: 'React Flow edges array', type: [Object] })
@IsArray()
edges: any[];
}
78 changes: 78 additions & 0 deletions apps/api/src/org-chart/org-chart.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
Controller,
Get,
Put,
Post,
Delete,
Body,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import {
ApiHeader,
ApiOperation,
ApiResponse,
ApiSecurity,
ApiTags,
} from '@nestjs/swagger';
import { OrganizationId } from '../auth/auth-context.decorator';
import { HybridAuthGuard } from '../auth/hybrid-auth.guard';
import { OrgChartService } from './org-chart.service';
import { UpsertOrgChartDto } from './dto/upsert-org-chart.dto';
import { UploadOrgChartDto } from './dto/upload-org-chart.dto';

@ApiTags('Org Chart')
@Controller({ path: 'org-chart', version: '1' })
@UseGuards(HybridAuthGuard)
@ApiSecurity('apikey')
@ApiHeader({
name: 'X-Organization-Id',
description:
'Organization ID (required for session auth, optional for API key auth)',
required: false,
})
export class OrgChartController {
constructor(private readonly orgChartService: OrgChartService) {}

@Get()
@ApiOperation({ summary: 'Get the organization chart' })
@ApiResponse({ status: 200, description: 'The organization chart' })
async getOrgChart(@OrganizationId() organizationId: string) {
return await this.orgChartService.findByOrganization(organizationId);
}

@Put()
@ApiOperation({ summary: 'Create or update an interactive organization chart' })
@ApiResponse({ status: 200, description: 'The saved organization chart' })
@UsePipes(new ValidationPipe({ whitelist: false, transform: false }))
async upsertOrgChart(
@OrganizationId() organizationId: string,
@Body() body: Record<string, unknown>,
) {
const dto: UpsertOrgChartDto = {
name: typeof body?.name === 'string' ? body.name : undefined,
nodes: Array.isArray(body?.nodes) ? body.nodes : [],
edges: Array.isArray(body?.edges) ? body.edges : [],
};
return await this.orgChartService.upsertInteractive(organizationId, dto);
}

@Post('upload')
@ApiOperation({ summary: 'Upload an image as the organization chart' })
@ApiResponse({ status: 201, description: 'The uploaded organization chart' })
@UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))
async uploadOrgChart(
@OrganizationId() organizationId: string,
@Body() dto: UploadOrgChartDto,
) {
return await this.orgChartService.uploadImage(organizationId, dto);
}

@Delete()
@ApiOperation({ summary: 'Delete the organization chart' })
@ApiResponse({ status: 200, description: 'Deletion confirmation' })
async deleteOrgChart(@OrganizationId() organizationId: string) {
return await this.orgChartService.delete(organizationId);
}
}
12 changes: 12 additions & 0 deletions apps/api/src/org-chart/org-chart.module.ts
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 { OrgChartController } from './org-chart.controller';
import { OrgChartService } from './org-chart.service';

@Module({
imports: [AuthModule],
controllers: [OrgChartController],
providers: [OrgChartService],
exports: [OrgChartService],
})
export class OrgChartModule {}
Loading
Loading