From 245f8f75b93cc970f3cfc78731e2231ed957cb17 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 3 Sep 2025 23:51:07 +0200 Subject: [PATCH 01/12] feat: create ai workflow api --- src/api/ai_workflow/aiWorkflow.service.ts | 47 +++++++++++++++++ src/api/ai_workflow/ai_workflow.controller.ts | 25 +++++++++ src/api/api.module.ts | 4 ++ src/dto/aiWorkflow.dto.ts | 52 +++++++++++++++++++ src/shared/enums/scopes.enum.ts | 3 ++ 5 files changed, 131 insertions(+) create mode 100644 src/api/ai_workflow/aiWorkflow.service.ts create mode 100644 src/api/ai_workflow/ai_workflow.controller.ts create mode 100644 src/dto/aiWorkflow.dto.ts diff --git a/src/api/ai_workflow/aiWorkflow.service.ts b/src/api/ai_workflow/aiWorkflow.service.ts new file mode 100644 index 0000000..f4b0c91 --- /dev/null +++ b/src/api/ai_workflow/aiWorkflow.service.ts @@ -0,0 +1,47 @@ +import { Injectable, BadRequestException } from '@nestjs/common'; +import { PrismaService } from '../../shared/modules/global/prisma.service'; +import { CreateAiWorkflowDto } from '../../dto/aiWorkflow.dto'; + +@Injectable() +export class AiWorkflowService { + constructor(private readonly prisma: PrismaService) {} + + async scorecardExists(scorecardId: string): Promise { + const count = await this.prisma.scorecard.count({ + where: { id: scorecardId }, + }); + return count > 0; + } + + async llmModelExists(llmId: string): Promise { + const count = await this.prisma.llmModel.count({ + where: { id: llmId }, + }); + return count > 0; + } + + async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { + const { scorecardId, llmId, createdBy, updatedBy, updatedAt, ...rest } = createAiWorkflowDto; + + const scorecardExists = await this.scorecardExists(scorecardId); + if (!scorecardExists) { + throw new BadRequestException(`Scorecard with id ${scorecardId} does not exist.`); + } + + 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: { + ...rest, + scorecardId, + llmId, + createdBy, + updatedBy: updatedBy || createdBy, + updatedAt: updatedAt || new Date(), + }, + }); + } +} diff --git a/src/api/ai_workflow/ai_workflow.controller.ts b/src/api/ai_workflow/ai_workflow.controller.ts new file mode 100644 index 0000000..5e92331 --- /dev/null +++ b/src/api/ai_workflow/ai_workflow.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { AiWorkflowService } from './aiWorkflow.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('/ai-workflow') +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); + } +} diff --git a/src/api/api.module.ts b/src/api/api.module.ts index d78d250..c2d82c8 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -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/aiWorkflow.service'; +import { AiWorkflowController } from './ai_workflow/ai_workflow.controller'; @Module({ imports: [HttpModule, GlobalProvidersModule, FileUploadModule], @@ -42,6 +44,7 @@ import { ScoreCardService } from './scorecard/scorecard.service'; ReviewApplicationController, ReviewHistoryController, WebhookController, + AiWorkflowController, ], providers: [ ReviewOpportunityService, @@ -53,6 +56,7 @@ import { ScoreCardService } from './scorecard/scorecard.service'; ScoreCardService, SubmissionService, ReviewSummationService, + AiWorkflowService, ], }) export class ApiModule {} diff --git a/src/dto/aiWorkflow.dto.ts b/src/dto/aiWorkflow.dto.ts new file mode 100644 index 0000000..8a15f86 --- /dev/null +++ b/src/dto/aiWorkflow.dto.ts @@ -0,0 +1,52 @@ +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; + + @ApiProperty() + @IsString() + @IsNotEmpty() + createdBy: string; + + @ApiProperty({ required: false }) + @IsString() + @IsNotEmpty() + updatedBy?: string; + + @ApiProperty({ required: false }) + updatedAt?: Date; +} diff --git a/src/shared/enums/scopes.enum.ts b/src/shared/enums/scopes.enum.ts index 4424271..bb258cc 100644 --- a/src/shared/enums/scopes.enum.ts +++ b/src/shared/enums/scopes.enum.ts @@ -63,6 +63,9 @@ export enum Scope { UpdateSubmission = 'update:submission', DeleteSubmission = 'delete:submission', AllSubmission = 'all:submission', + + // AI workflow scopes + CreateWorkflow = 'create:workflow', } /** From 14c164101268c1b25e92cf3e44004fda9e317b91 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Sep 2025 19:09:03 +0200 Subject: [PATCH 02/12] deploy to dev --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f2d5041..5382ee4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,6 +75,7 @@ workflows: only: - develop - feat/ai-workflows + - pm-1793 - 'build-prod': context: org-global From 61722693128d2c339d414e3daca1cb2157670760 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Sep 2025 19:16:58 +0200 Subject: [PATCH 03/12] fix: lint --- prisma/migrate.ts | 85 +++++++------------ src/api/ai_workflow/aiWorkflow.service.ts | 11 ++- src/api/ai_workflow/ai_workflow.controller.ts | 12 ++- 3 files changed, 51 insertions(+), 57 deletions(-) diff --git a/prisma/migrate.ts b/prisma/migrate.ts index f0629f6..60ae81e 100644 --- a/prisma/migrate.ts +++ b/prisma/migrate.ts @@ -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'], @@ -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; @@ -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, @@ -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]}`, ); @@ -1402,15 +1395,11 @@ async function processType(type: string, subtype?: string) { case 'llm_model': { 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); - 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), @@ -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) { @@ -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]}`, ); @@ -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, @@ -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]}`, ); @@ -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 }, ].forEach((f) => { if (!fs.existsSync('.tmp')) { fs.mkdirSync('.tmp'); diff --git a/src/api/ai_workflow/aiWorkflow.service.ts b/src/api/ai_workflow/aiWorkflow.service.ts index f4b0c91..150c87a 100644 --- a/src/api/ai_workflow/aiWorkflow.service.ts +++ b/src/api/ai_workflow/aiWorkflow.service.ts @@ -21,16 +21,21 @@ export class AiWorkflowService { } async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { - const { scorecardId, llmId, createdBy, updatedBy, updatedAt, ...rest } = createAiWorkflowDto; + const { scorecardId, llmId, createdBy, updatedBy, updatedAt, ...rest } = + createAiWorkflowDto; const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) { - throw new BadRequestException(`Scorecard with id ${scorecardId} does not exist.`); + throw new BadRequestException( + `Scorecard with id ${scorecardId} does not exist.`, + ); } const llmExists = await this.llmModelExists(llmId); if (!llmExists) { - throw new BadRequestException(`LLM model with id ${llmId} does not exist.`); + throw new BadRequestException( + `LLM model with id ${llmId} does not exist.`, + ); } return this.prisma.aiWorkflow.create({ diff --git a/src/api/ai_workflow/ai_workflow.controller.ts b/src/api/ai_workflow/ai_workflow.controller.ts index 5e92331..1b0cad4 100644 --- a/src/api/ai_workflow/ai_workflow.controller.ts +++ b/src/api/ai_workflow/ai_workflow.controller.ts @@ -1,5 +1,10 @@ import { Controller, Post, Body } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiTags, + ApiOperation, + ApiResponse, +} from '@nestjs/swagger'; import { AiWorkflowService } from './aiWorkflow.service'; import { CreateAiWorkflowDto } from '../../dto/aiWorkflow.dto'; import { Scopes } from 'src/shared/decorators/scopes.decorator'; @@ -17,7 +22,10 @@ export class AiWorkflowController { @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: 201, + description: 'The AI workflow has been successfully created.', + }) @ApiResponse({ status: 403, description: 'Forbidden.' }) async create(@Body() createAiWorkflowDto: CreateAiWorkflowDto) { return this.aiWorkflowService.createWithValidation(createAiWorkflowDto); From b7dfcf61bc8c75ab8accf6a55ce322d74ea2317e Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Sep 2025 23:33:51 +0200 Subject: [PATCH 04/12] fix: errors --- src/api/ai_workflow/aiWorkflow.service.ts | 5 +++-- src/dto/aiWorkflow.dto.ts | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api/ai_workflow/aiWorkflow.service.ts b/src/api/ai_workflow/aiWorkflow.service.ts index 150c87a..e426d35 100644 --- a/src/api/ai_workflow/aiWorkflow.service.ts +++ b/src/api/ai_workflow/aiWorkflow.service.ts @@ -1,6 +1,7 @@ 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 { @@ -8,7 +9,7 @@ export class AiWorkflowService { async scorecardExists(scorecardId: string): Promise { const count = await this.prisma.scorecard.count({ - where: { id: scorecardId }, + where: { id: scorecardId, status: ScorecardStatus.ACTIVE}, }); return count > 0; } @@ -27,7 +28,7 @@ export class AiWorkflowService { const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) { throw new BadRequestException( - `Scorecard with id ${scorecardId} does not exist.`, + `Active scorecard with id ${scorecardId} does not exist.`, ); } diff --git a/src/dto/aiWorkflow.dto.ts b/src/dto/aiWorkflow.dto.ts index 8a15f86..bdd0d21 100644 --- a/src/dto/aiWorkflow.dto.ts +++ b/src/dto/aiWorkflow.dto.ts @@ -43,8 +43,6 @@ export class CreateAiWorkflowDto { createdBy: string; @ApiProperty({ required: false }) - @IsString() - @IsNotEmpty() updatedBy?: string; @ApiProperty({ required: false }) From 0ac583db99dc496bc24daf06bee768fb78032e57 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Sep 2025 23:37:07 +0200 Subject: [PATCH 05/12] fix: lint --- src/api/ai_workflow/aiWorkflow.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ai_workflow/aiWorkflow.service.ts b/src/api/ai_workflow/aiWorkflow.service.ts index e426d35..3812774 100644 --- a/src/api/ai_workflow/aiWorkflow.service.ts +++ b/src/api/ai_workflow/aiWorkflow.service.ts @@ -9,7 +9,7 @@ export class AiWorkflowService { async scorecardExists(scorecardId: string): Promise { const count = await this.prisma.scorecard.count({ - where: { id: scorecardId, status: ScorecardStatus.ACTIVE}, + where: { id: scorecardId, status: ScorecardStatus.ACTIVE }, }); return count > 0; } From 7fa9f50476d19907afdf495f151fd5327186a631 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 4 Sep 2025 23:54:20 +0200 Subject: [PATCH 06/12] fix: modified api url --- src/api/ai_workflow/ai_workflow.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ai_workflow/ai_workflow.controller.ts b/src/api/ai_workflow/ai_workflow.controller.ts index 1b0cad4..09df85f 100644 --- a/src/api/ai_workflow/ai_workflow.controller.ts +++ b/src/api/ai_workflow/ai_workflow.controller.ts @@ -14,7 +14,7 @@ import { Roles } from 'src/shared/guards/tokenRoles.guard'; @ApiTags('ai_workflow') @ApiBearerAuth() -@Controller('/ai-workflow') +@Controller('/workflows') export class AiWorkflowController { constructor(private readonly aiWorkflowService: AiWorkflowService) {} From bd57ad1e6749a33cf5b3fc0776be7d4cefc84905 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 9 Sep 2025 20:54:08 +0200 Subject: [PATCH 07/12] fix: review comments --- .../migration.sql | 11 ++++ prisma/schema.prisma | 60 +++++++++---------- .../ai-workflow.controller.ts} | 2 +- .../ai-workflow.service.ts} | 5 +- src/api/api.module.ts | 4 +- src/dto/aiWorkflow.dto.ts | 6 -- 6 files changed, 45 insertions(+), 43 deletions(-) create mode 100644 prisma/migrations/20250909154525_updated_audit_fields/migration.sql rename src/api/{ai_workflow/ai_workflow.controller.ts => ai-workflow/ai-workflow.controller.ts} (94%) rename src/api/{ai_workflow/aiWorkflow.service.ts => ai-workflow/ai-workflow.service.ts} (88%) diff --git a/prisma/migrations/20250909154525_updated_audit_fields/migration.sql b/prisma/migrations/20250909154525_updated_audit_fields/migration.sql new file mode 100644 index 0000000..fa6fa45 --- /dev/null +++ b/prisma/migrations/20250909154525_updated_audit_fields/migration.sql @@ -0,0 +1,11 @@ +-- AlterTable +ALTER TABLE "aiWorkflow" ALTER COLUMN "updatedAt" DROP NOT NULL, +ALTER COLUMN "updatedBy" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "aiWorkflowRunItem" ALTER COLUMN "createdAt" DROP NOT NULL, +ALTER COLUMN "createdBy" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "aiWorkflowRunItemComment" ALTER COLUMN "updatedAt" DROP NOT NULL, +ALTER COLUMN "updatedBy" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f244e7a..b1e8c00 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -570,18 +570,18 @@ model llmModel { } model aiWorkflow { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - name String @unique @db.VarChar - llmId String @db.VarChar(14) - description String @db.Text - defUrl String @db.VarChar - gitId String @db.VarChar - gitOwner String @db.VarChar - scorecardId String @db.VarChar(14) - createdAt DateTime @default(now()) @db.Timestamp(3) - createdBy String @db.Text - updatedAt DateTime @db.Timestamp(3) - updatedBy String @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + name String @unique @db.VarChar + llmId String @db.VarChar(14) + description String @db.Text + defUrl String @db.VarChar + gitId String @db.VarChar + gitOwner String @db.VarChar + scorecardId String @db.VarChar(14) + createdAt DateTime @default(now()) @db.Timestamp(3) + createdBy String @db.Text + updatedAt DateTime? @db.Timestamp(3) + updatedBy String? @db.Text llm llmModel @relation(fields: [llmId], references: [id]) scorecard scorecard @relation(fields: [scorecardId], references: [id]) @@ -604,15 +604,15 @@ model aiWorkflowRun { } model aiWorkflowRunItem { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - workflowRunId String @db.VarChar(14) - scorecardQuestionId String @db.VarChar(14) - content String @db.Text - upVotes Int @default(0) - downVotes Int @default(0) - questionScore Float? @db.DoublePrecision - createdAt DateTime @db.Timestamp(3) - createdBy String @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + workflowRunId String @db.VarChar(14) + scorecardQuestionId String @db.VarChar(14) + content String @db.Text + upVotes Int @default(0) + downVotes Int @default(0) + questionScore Float? @db.DoublePrecision + createdAt DateTime? @db.Timestamp(3) + createdBy String? @db.Text run aiWorkflowRun @relation(fields: [workflowRunId], references: [id]) question scorecardQuestion @relation(fields: [scorecardQuestionId], references: [id]) @@ -620,15 +620,15 @@ model aiWorkflowRunItem { } model aiWorkflowRunItemComment { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - workflowRunItemId String @db.VarChar(14) - userId String @db.Text - content String @db.Text - parentId String? @db.VarChar(14) - createdAt DateTime @default(now()) @db.Timestamp(3) - createdBy String @db.Text - updatedAt DateTime @db.Timestamp(3) - updatedBy String @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + workflowRunItemId String @db.VarChar(14) + userId String @db.Text + content String @db.Text + parentId String? @db.VarChar(14) + createdAt DateTime @default(now()) @db.Timestamp(3) + createdBy String @db.Text + updatedAt DateTime? @db.Timestamp(3) + updatedBy String? @db.Text item aiWorkflowRunItem @relation(fields: [workflowRunItemId], references: [id]) parent aiWorkflowRunItemComment? @relation("CommentHierarchy", fields: [parentId], references: [id]) diff --git a/src/api/ai_workflow/ai_workflow.controller.ts b/src/api/ai-workflow/ai-workflow.controller.ts similarity index 94% rename from src/api/ai_workflow/ai_workflow.controller.ts rename to src/api/ai-workflow/ai-workflow.controller.ts index 09df85f..e1e61e4 100644 --- a/src/api/ai_workflow/ai_workflow.controller.ts +++ b/src/api/ai-workflow/ai-workflow.controller.ts @@ -5,7 +5,7 @@ import { ApiOperation, ApiResponse, } from '@nestjs/swagger'; -import { AiWorkflowService } from './aiWorkflow.service'; +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'; diff --git a/src/api/ai_workflow/aiWorkflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts similarity index 88% rename from src/api/ai_workflow/aiWorkflow.service.ts rename to src/api/ai-workflow/ai-workflow.service.ts index 3812774..3d05136 100644 --- a/src/api/ai_workflow/aiWorkflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -22,7 +22,7 @@ export class AiWorkflowService { } async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { - const { scorecardId, llmId, createdBy, updatedBy, updatedAt, ...rest } = + const { scorecardId, llmId, ...rest } = createAiWorkflowDto; const scorecardExists = await this.scorecardExists(scorecardId); @@ -44,9 +44,6 @@ export class AiWorkflowService { ...rest, scorecardId, llmId, - createdBy, - updatedBy: updatedBy || createdBy, - updatedAt: updatedAt || new Date(), }, }); } diff --git a/src/api/api.module.ts b/src/api/api.module.ts index c2d82c8..2d9d30d 100644 --- a/src/api/api.module.ts +++ b/src/api/api.module.ts @@ -25,8 +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/aiWorkflow.service'; -import { AiWorkflowController } from './ai_workflow/ai_workflow.controller'; +import { AiWorkflowService } from './ai-workflow/ai-workflow.service'; +import { AiWorkflowController } from './ai-workflow/ai-workflow.controller'; @Module({ imports: [HttpModule, GlobalProvidersModule, FileUploadModule], diff --git a/src/dto/aiWorkflow.dto.ts b/src/dto/aiWorkflow.dto.ts index bdd0d21..8c33e2e 100644 --- a/src/dto/aiWorkflow.dto.ts +++ b/src/dto/aiWorkflow.dto.ts @@ -41,10 +41,4 @@ export class CreateAiWorkflowDto { @IsString() @IsNotEmpty() createdBy: string; - - @ApiProperty({ required: false }) - updatedBy?: string; - - @ApiProperty({ required: false }) - updatedAt?: Date; } From 71baf40e45aa3fff9893bc7b94e2ca5cb6ac5827 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 9 Sep 2025 20:54:37 +0200 Subject: [PATCH 08/12] fix: lint --- src/api/ai-workflow/ai-workflow.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index 3d05136..5033450 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -22,8 +22,7 @@ export class AiWorkflowService { } async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { - const { scorecardId, llmId, ...rest } = - createAiWorkflowDto; + const { scorecardId, llmId, ...rest } = createAiWorkflowDto; const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) { From 1e407f0970cfc50859d1a190f85ffdf20851c959 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Tue, 9 Sep 2025 20:55:40 +0200 Subject: [PATCH 09/12] fix: lint --- src/api/ai-workflow/ai-workflow.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index 5033450..823f2eb 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -27,7 +27,7 @@ export class AiWorkflowService { const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) { throw new BadRequestException( - `Active scorecard with id ${scorecardId} does not exist.`, + `Scorecard with id ${scorecardId} does not exist or is not active.`, ); } From f1b5c620790a7e8c8392c3e1204ba16e42cd5edc Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Wed, 10 Sep 2025 22:51:02 +0200 Subject: [PATCH 10/12] fix: removed last migration --- .circleci/config.yml | 1 - .../migration.sql | 11 --------- prisma/schema.prisma | 24 +++++++++---------- src/api/ai-workflow/ai-workflow.service.ts | 12 ++++++++-- src/dto/aiWorkflow.dto.ts | 5 ---- 5 files changed, 22 insertions(+), 31 deletions(-) delete mode 100644 prisma/migrations/20250909154525_updated_audit_fields/migration.sql diff --git a/.circleci/config.yml b/.circleci/config.yml index 2166184..4d90337 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,7 +75,6 @@ workflows: only: - develop - feat/ai-workflows - - pm-1793 - 'build-prod': context: org-global diff --git a/prisma/migrations/20250909154525_updated_audit_fields/migration.sql b/prisma/migrations/20250909154525_updated_audit_fields/migration.sql deleted file mode 100644 index fa6fa45..0000000 --- a/prisma/migrations/20250909154525_updated_audit_fields/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- AlterTable -ALTER TABLE "aiWorkflow" ALTER COLUMN "updatedAt" DROP NOT NULL, -ALTER COLUMN "updatedBy" DROP NOT NULL; - --- AlterTable -ALTER TABLE "aiWorkflowRunItem" ALTER COLUMN "createdAt" DROP NOT NULL, -ALTER COLUMN "createdBy" DROP NOT NULL; - --- AlterTable -ALTER TABLE "aiWorkflowRunItemComment" ALTER COLUMN "updatedAt" DROP NOT NULL, -ALTER COLUMN "updatedBy" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b1e8c00..f2bf141 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -570,18 +570,18 @@ model llmModel { } model aiWorkflow { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - name String @unique @db.VarChar - llmId String @db.VarChar(14) - description String @db.Text - defUrl String @db.VarChar - gitId String @db.VarChar - gitOwner String @db.VarChar - scorecardId String @db.VarChar(14) - createdAt DateTime @default(now()) @db.Timestamp(3) - createdBy String @db.Text - updatedAt DateTime? @db.Timestamp(3) - updatedBy String? @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + name String @unique @db.VarChar + llmId String @db.VarChar(14) + description String @db.Text + defUrl String @db.VarChar + gitId String @db.VarChar + gitOwner String @db.VarChar + scorecardId String @db.VarChar(14) + createdAt DateTime @default(now()) @db.Timestamp(3) + createdBy String @db.Text + updatedAt DateTime @db.Timestamp(3) + updatedBy String @db.Text llm llmModel @relation(fields: [llmId], references: [id]) scorecard scorecard @relation(fields: [scorecardId], references: [id]) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index 823f2eb..d0944f0 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -22,7 +22,7 @@ export class AiWorkflowService { } async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { - const { scorecardId, llmId, ...rest } = createAiWorkflowDto; + const { scorecardId, llmId, name, description, defUrl, gitId, gitOwner } = createAiWorkflowDto; const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) { @@ -40,9 +40,17 @@ export class AiWorkflowService { return this.prisma.aiWorkflow.create({ data: { - ...rest, + defUrl, + description, + gitId, + gitOwner, + name, scorecardId, llmId, + // TODO: This has to be removed once the prisma middleware is implemented + createdBy: '', + updatedAt: '', + updatedBy: '', }, }); } diff --git a/src/dto/aiWorkflow.dto.ts b/src/dto/aiWorkflow.dto.ts index 8c33e2e..5f16fa4 100644 --- a/src/dto/aiWorkflow.dto.ts +++ b/src/dto/aiWorkflow.dto.ts @@ -36,9 +36,4 @@ export class CreateAiWorkflowDto { @IsString() @IsNotEmpty() scorecardId: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - createdBy: string; } From 0a7864c9f1f414afa80354027ea45345ac4da07f Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 11 Sep 2025 00:22:26 +0200 Subject: [PATCH 11/12] fix: removed last migration --- prisma/schema.prisma | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f2bf141..f244e7a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -604,15 +604,15 @@ model aiWorkflowRun { } model aiWorkflowRunItem { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - workflowRunId String @db.VarChar(14) - scorecardQuestionId String @db.VarChar(14) - content String @db.Text - upVotes Int @default(0) - downVotes Int @default(0) - questionScore Float? @db.DoublePrecision - createdAt DateTime? @db.Timestamp(3) - createdBy String? @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + workflowRunId String @db.VarChar(14) + scorecardQuestionId String @db.VarChar(14) + content String @db.Text + upVotes Int @default(0) + downVotes Int @default(0) + questionScore Float? @db.DoublePrecision + createdAt DateTime @db.Timestamp(3) + createdBy String @db.Text run aiWorkflowRun @relation(fields: [workflowRunId], references: [id]) question scorecardQuestion @relation(fields: [scorecardQuestionId], references: [id]) @@ -620,15 +620,15 @@ model aiWorkflowRunItem { } model aiWorkflowRunItemComment { - id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) - workflowRunItemId String @db.VarChar(14) - userId String @db.Text - content String @db.Text - parentId String? @db.VarChar(14) - createdAt DateTime @default(now()) @db.Timestamp(3) - createdBy String @db.Text - updatedAt DateTime? @db.Timestamp(3) - updatedBy String? @db.Text + id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14) + workflowRunItemId String @db.VarChar(14) + userId String @db.Text + content String @db.Text + parentId String? @db.VarChar(14) + createdAt DateTime @default(now()) @db.Timestamp(3) + createdBy String @db.Text + updatedAt DateTime @db.Timestamp(3) + updatedBy String @db.Text item aiWorkflowRunItem @relation(fields: [workflowRunItemId], references: [id]) parent aiWorkflowRunItemComment? @relation("CommentHierarchy", fields: [parentId], references: [id]) From 89150924b1771cac6d6221571522d760ac7f1c11 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 11 Sep 2025 00:26:16 +0200 Subject: [PATCH 12/12] fix: lint --- src/api/ai-workflow/ai-workflow.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/ai-workflow/ai-workflow.service.ts b/src/api/ai-workflow/ai-workflow.service.ts index d0944f0..6e82176 100644 --- a/src/api/ai-workflow/ai-workflow.service.ts +++ b/src/api/ai-workflow/ai-workflow.service.ts @@ -22,7 +22,8 @@ export class AiWorkflowService { } async createWithValidation(createAiWorkflowDto: CreateAiWorkflowDto) { - const { scorecardId, llmId, name, description, defUrl, gitId, gitOwner } = createAiWorkflowDto; + const { scorecardId, llmId, name, description, defUrl, gitId, gitOwner } = + createAiWorkflowDto; const scorecardExists = await this.scorecardExists(scorecardId); if (!scorecardExists) {