Skip to content
85 changes: 33 additions & 52 deletions prisma/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const modelMappingKeys = [
'review_item_comment',
'llm_provider',
'llm_model',
'ai_workflow'
'ai_workflow',
];
const subModelMappingKeys = {
review_item_comment: ['reviewItemComment', 'appeal', 'appealResponse'],
Expand Down Expand Up @@ -813,27 +813,26 @@ async function processType(type: string, subtype?: string) {
}
case 'scorecard': {
console.log(`[${type}][${file}] Processing file`);
const processedData = jsonData[key]
.map((sc) => {
const id = nanoid(14);
scorecardIdMap.set(sc.scorecard_id, id);
return {
id: id,
legacyId: sc.scorecard_id,
status: scorecardStatusMap[sc.scorecard_status_id],
type: scorecardTypeMap[sc.scorecard_type_id],
challengeTrack: projectCategoryMap[sc.project_category_id].type,
challengeType: projectCategoryMap[sc.project_category_id].name,
name: sc.name,
version: sc.version,
minScore: parseFloat(sc.min_score),
maxScore: parseFloat(sc.max_score),
createdAt: new Date(sc.create_date),
createdBy: sc.create_user,
updatedAt: new Date(sc.modify_date),
updatedBy: sc.modify_user,
};
});
const processedData = jsonData[key].map((sc) => {
const id = nanoid(14);
scorecardIdMap.set(sc.scorecard_id, id);
return {
id: id,
legacyId: sc.scorecard_id,
status: scorecardStatusMap[sc.scorecard_status_id],
type: scorecardTypeMap[sc.scorecard_type_id],
challengeTrack: projectCategoryMap[sc.project_category_id].type,
challengeType: projectCategoryMap[sc.project_category_id].name,
name: sc.name,
version: sc.version,
minScore: parseFloat(sc.min_score),
maxScore: parseFloat(sc.max_score),
createdAt: new Date(sc.create_date),
createdBy: sc.create_user,
updatedAt: new Date(sc.modify_date),
updatedBy: sc.modify_user,
};
});
const totalBatches = Math.ceil(processedData.length / batchSize);
for (let i = 0; i < processedData.length; i += batchSize) {
const batchIndex = i / batchSize + 1;
Expand Down Expand Up @@ -1350,13 +1349,9 @@ async function processType(type: string, subtype?: string) {
case 'llm_provider': {
console.log(`[${type}][${subtype}][${file}] Processing file`);
const idToLegacyIdMap = {};
const processedData = jsonData[key]
.map((c) => {
const processedData = jsonData[key].map((c) => {
const id = nanoid(14);
llmProviderIdMap.set(
c.llm_provider_id,
id,
);
llmProviderIdMap.set(c.llm_provider_id, id);
idToLegacyIdMap[id] = c.llm_provider_id;
return {
id: id,
Expand Down Expand Up @@ -1387,9 +1382,7 @@ async function processType(type: string, subtype?: string) {
data: item,
})
.catch((err) => {
llmProviderIdMap.delete(
idToLegacyIdMap[item.id],
);
llmProviderIdMap.delete(idToLegacyIdMap[item.id]);
console.error(
`[${type}][${subtype}][${file}] Error code: ${err.code}, LegacyId: ${idToLegacyIdMap[item.id]}`,
);
Expand All @@ -1402,15 +1395,11 @@ async function processType(type: string, subtype?: string) {
case 'llm_model': {
console.log(`[${type}][${subtype}][${file}] Processing file`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log statement uses the variable file, but file is not defined within the current scope. Ensure that file is defined or passed to the function if needed.

const idToLegacyIdMap = {};
const processedData = jsonData[key]
.map((c) => {
const processedData = jsonData[key].map((c) => {
const id = nanoid(14);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name than c for better readability and maintainability of the code.

llmModelIdMap.set(
c.llm_model_id,
id,
);
llmModelIdMap.set(c.llm_model_id, id);
idToLegacyIdMap[id] = c.llm_model_id;
console.log(llmProviderIdMap.get(c.provider_id), 'c.provider_id')
console.log(llmProviderIdMap.get(c.provider_id), 'c.provider_id');
return {
id: id,
providerId: llmProviderIdMap.get(c.provider_id),
Expand All @@ -1423,7 +1412,7 @@ async function processType(type: string, subtype?: string) {
};
});

console.log(llmProviderIdMap, processedData, 'processedData')
console.log(llmProviderIdMap, processedData, 'processedData');

const totalBatches = Math.ceil(processedData.length / batchSize);
for (let i = 0; i < processedData.length; i += batchSize) {
Expand All @@ -1446,9 +1435,7 @@ async function processType(type: string, subtype?: string) {
data: item,
})
.catch((err) => {
llmModelIdMap.delete(
idToLegacyIdMap[item.id],
);
llmModelIdMap.delete(idToLegacyIdMap[item.id]);
console.error(
`[${type}][${subtype}][${file}] Error code: ${err.code}, LegacyId: ${idToLegacyIdMap[item.id]}`,
);
Expand All @@ -1461,13 +1448,9 @@ async function processType(type: string, subtype?: string) {
case 'ai_workflow': {
console.log(`[${type}][${subtype}][${file}] Processing file`);
const idToLegacyIdMap = {};
const processedData = jsonData[key]
.map((c) => {
const processedData = jsonData[key].map((c) => {
const id = nanoid(14);
aiWorkflowIdMap.set(
c.ai_workflow_id,
id,
);
aiWorkflowIdMap.set(c.ai_workflow_id, id);
idToLegacyIdMap[id] = c.ai_workflow_id;
return {
id: id,
Expand Down Expand Up @@ -1506,9 +1489,7 @@ async function processType(type: string, subtype?: string) {
data: item,
})
.catch((err) => {
aiWorkflowIdMap.delete(
idToLegacyIdMap[item.id],
);
aiWorkflowIdMap.delete(idToLegacyIdMap[item.id]);
console.error(
`[${type}][${subtype}][${file}] Error code: ${err.code}, LegacyId: ${idToLegacyIdMap[item.id]}`,
);
Expand Down Expand Up @@ -1687,7 +1668,7 @@ migrate()
{ key: 'submissionIdMap', value: submissionIdMap },
{ key: 'llmProviderIdMap', value: llmProviderIdMap },
{ key: 'llmModelIdMap', value: llmModelIdMap },
{ key: 'aiWorkflowIdMap', value: aiWorkflowIdMap }
{ key: 'aiWorkflowIdMap', value: aiWorkflowIdMap },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an extra comma at the end of the object in line 1671. This may lead to syntax errors in environments that do not support trailing commas. Consider removing the trailing comma.

].forEach((f) => {
if (!fs.existsSync('.tmp')) {
fs.mkdirSync('.tmp');
Expand Down
33 changes: 33 additions & 0 deletions src/api/ai-workflow/ai-workflow.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller, Post, Body } from '@nestjs/common';
import {
ApiBearerAuth,
ApiTags,
ApiOperation,
ApiResponse,
} from '@nestjs/swagger';
import { AiWorkflowService } from './ai-workflow.service';
import { CreateAiWorkflowDto } from '../../dto/aiWorkflow.dto';
import { Scopes } from 'src/shared/decorators/scopes.decorator';
import { UserRole } from 'src/shared/enums/userRole.enum';
import { Scope } from 'src/shared/enums/scopes.enum';
import { Roles } from 'src/shared/guards/tokenRoles.guard';

@ApiTags('ai_workflow')
@ApiBearerAuth()
@Controller('/workflows')
export class AiWorkflowController {
constructor(private readonly aiWorkflowService: AiWorkflowService) {}

@Post()
@Roles(UserRole.Admin)
@Scopes(Scope.CreateWorkflow)
@ApiOperation({ summary: 'Create a new AI workflow' })
@ApiResponse({
status: 201,
description: 'The AI workflow has been successfully created.',
})
@ApiResponse({ status: 403, description: 'Forbidden.' })
async create(@Body() createAiWorkflowDto: CreateAiWorkflowDto) {
return this.aiWorkflowService.createWithValidation(createAiWorkflowDto);
}
}
58 changes: 58 additions & 0 deletions src/api/ai-workflow/ai-workflow.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../shared/modules/global/prisma.service';
import { CreateAiWorkflowDto } from '../../dto/aiWorkflow.dto';
import { ScorecardStatus } from 'src/dto/scorecard.dto';

@Injectable()
export class AiWorkflowService {
constructor(private readonly prisma: PrismaService) {}

async scorecardExists(scorecardId: string): Promise<boolean> {
const count = await this.prisma.scorecard.count({
where: { id: scorecardId, status: ScorecardStatus.ACTIVE },
});
return count > 0;
}

async llmModelExists(llmId: string): Promise<boolean> {
const count = await this.prisma.llmModel.count({
where: { id: llmId },
});
return count > 0;
}

async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) {
const { scorecardId, llmId, name, description, defUrl, gitId, gitOwner } =
createAiWorkflowDto;

const scorecardExists = await this.scorecardExists(scorecardId);
if (!scorecardExists) {
throw new BadRequestException(
`Scorecard with id ${scorecardId} does not exist or is not active.`,
);
}

const llmExists = await this.llmModelExists(llmId);
if (!llmExists) {
throw new BadRequestException(
`LLM model with id ${llmId} does not exist.`,
);
}

return this.prisma.aiWorkflow.create({
data: {
defUrl,
description,
gitId,
gitOwner,
name,
scorecardId,
llmId,
// TODO: This has to be removed once the prisma middleware is implemented
createdBy: '',
updatedAt: '',
updatedBy: '',
},
});
}
}
4 changes: 4 additions & 0 deletions src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { WebhookController } from './webhook/webhook.controller';
import { WebhookService } from './webhook/webhook.service';
import { GiteaWebhookAuthGuard } from '../shared/guards/gitea-webhook-auth.guard';
import { ScoreCardService } from './scorecard/scorecard.service';
import { AiWorkflowService } from './ai-workflow/ai-workflow.service';
import { AiWorkflowController } from './ai-workflow/ai-workflow.controller';

@Module({
imports: [HttpModule, GlobalProvidersModule, FileUploadModule],
Expand All @@ -42,6 +44,7 @@ import { ScoreCardService } from './scorecard/scorecard.service';
ReviewApplicationController,
ReviewHistoryController,
WebhookController,
AiWorkflowController,
],
providers: [
ReviewOpportunityService,
Expand All @@ -53,6 +56,7 @@ import { ScoreCardService } from './scorecard/scorecard.service';
ScoreCardService,
SubmissionService,
ReviewSummationService,
AiWorkflowService,
],
})
export class ApiModule {}
39 changes: 39 additions & 0 deletions src/dto/aiWorkflow.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';

export class CreateAiWorkflowDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
name: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
llmId: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
description: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
defUrl: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
gitId: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
gitOwner: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
scorecardId: string;
}
3 changes: 3 additions & 0 deletions src/shared/enums/scopes.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export enum Scope {
UpdateSubmission = 'update:submission',
DeleteSubmission = 'delete:submission',
AllSubmission = 'all:submission',

// AI workflow scopes
CreateWorkflow = 'create:workflow',
}

/**
Expand Down