generated from fahricansecer/boilerplate-be
main
This commit is contained in:
208
src/modules/skriptai/controllers/jobs.controller.ts
Normal file
208
src/modules/skriptai/controllers/jobs.controller.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Param,
|
||||
Body,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Queue } from 'bullmq';
|
||||
import {
|
||||
QUEUES,
|
||||
JobType,
|
||||
JobStatus,
|
||||
} from '../queue/queue.constants';
|
||||
|
||||
/**
|
||||
* JobsController
|
||||
*
|
||||
* REST API for managing async AI jobs.
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /jobs/submit — Submit a new async job
|
||||
* - GET /jobs/:id/status — Check job status & progress
|
||||
* - GET /jobs/:id/result — Get job result
|
||||
*
|
||||
* TR: Asenkron AI işlerini yönetmek için REST API.
|
||||
*/
|
||||
@ApiTags('SkriptAI - Jobs')
|
||||
@ApiBearerAuth()
|
||||
@Controller('skriptai/jobs')
|
||||
export class JobsController {
|
||||
private readonly logger = new Logger(JobsController.name);
|
||||
|
||||
constructor(
|
||||
@InjectQueue(QUEUES.SCRIPT_GENERATION)
|
||||
private readonly scriptQueue: Queue,
|
||||
@InjectQueue(QUEUES.DEEP_RESEARCH)
|
||||
private readonly researchQueue: Queue,
|
||||
@InjectQueue(QUEUES.ANALYSIS)
|
||||
private readonly analysisQueue: Queue,
|
||||
@InjectQueue(QUEUES.IMAGE_GENERATION)
|
||||
private readonly imageQueue: Queue,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Submit a new async job
|
||||
*/
|
||||
@Post('submit')
|
||||
@ApiOperation({ summary: 'Submit an async AI job' })
|
||||
async submitJob(
|
||||
@Body()
|
||||
body: {
|
||||
type: JobType;
|
||||
payload: Record<string, any>;
|
||||
},
|
||||
) {
|
||||
const { type, payload } = body;
|
||||
const queue = this.getQueueForJobType(type);
|
||||
|
||||
const job = await queue.add(type, payload, {
|
||||
attempts: 2,
|
||||
backoff: { type: 'exponential', delay: 5000 },
|
||||
removeOnComplete: { age: 3600 }, // 1 hour
|
||||
removeOnFail: { age: 86400 }, // 24 hours
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Job submitted: ${job.id} (${type}) — payload: ${JSON.stringify(payload)}`,
|
||||
);
|
||||
|
||||
return {
|
||||
jobId: job.id,
|
||||
type,
|
||||
status: JobStatus.QUEUED,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check job status and progress
|
||||
*/
|
||||
@Get(':id/status')
|
||||
@ApiOperation({ summary: 'Check job status & progress' })
|
||||
async getJobStatus(@Param('id') jobId: string) {
|
||||
const job = await this.findJobById(jobId);
|
||||
|
||||
if (!job) {
|
||||
throw new NotFoundException(`Job ${jobId} not found`);
|
||||
}
|
||||
|
||||
const state = await job.getState();
|
||||
const progress = job.progress;
|
||||
|
||||
return {
|
||||
jobId: job.id,
|
||||
type: job.name,
|
||||
status: this.mapBullState(state),
|
||||
progress: progress || null,
|
||||
createdAt: new Date(job.timestamp).toISOString(),
|
||||
processedOn: job.processedOn
|
||||
? new Date(job.processedOn).toISOString()
|
||||
: null,
|
||||
finishedOn: job.finishedOn
|
||||
? new Date(job.finishedOn).toISOString()
|
||||
: null,
|
||||
failedReason: job.failedReason || null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get job result
|
||||
*/
|
||||
@Get(':id/result')
|
||||
@ApiOperation({ summary: 'Get completed job result' })
|
||||
async getJobResult(@Param('id') jobId: string) {
|
||||
const job = await this.findJobById(jobId);
|
||||
|
||||
if (!job) {
|
||||
throw new NotFoundException(`Job ${jobId} not found`);
|
||||
}
|
||||
|
||||
const state = await job.getState();
|
||||
|
||||
if (state !== 'completed') {
|
||||
return {
|
||||
jobId: job.id,
|
||||
status: this.mapBullState(state),
|
||||
result: null,
|
||||
message: 'Job has not completed yet',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
jobId: job.id,
|
||||
status: JobStatus.COMPLETED,
|
||||
result: job.returnvalue,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== HELPERS ==========
|
||||
|
||||
private getQueueForJobType(type: JobType): Queue {
|
||||
if (
|
||||
type === JobType.GENERATE_SCRIPT ||
|
||||
type === JobType.REGENERATE_SEGMENT ||
|
||||
type === JobType.REGENERATE_PARTIAL ||
|
||||
type === JobType.REWRITE_SEGMENT
|
||||
) {
|
||||
return this.scriptQueue;
|
||||
}
|
||||
|
||||
if (
|
||||
type === JobType.DEEP_RESEARCH ||
|
||||
type === JobType.DISCOVER_QUESTIONS
|
||||
) {
|
||||
return this.researchQueue;
|
||||
}
|
||||
|
||||
if (
|
||||
type === JobType.NEURO_ANALYSIS ||
|
||||
type === JobType.YOUTUBE_AUDIT ||
|
||||
type === JobType.COMMERCIAL_BRIEF ||
|
||||
type === JobType.GENERATE_VISUAL_ASSETS
|
||||
) {
|
||||
return this.analysisQueue;
|
||||
}
|
||||
|
||||
if (
|
||||
type === JobType.GENERATE_SEGMENT_IMAGE ||
|
||||
type === JobType.GENERATE_THUMBNAIL
|
||||
) {
|
||||
return this.imageQueue;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown job type: ${type}`);
|
||||
}
|
||||
|
||||
private async findJobById(jobId: string) {
|
||||
const queues = [
|
||||
this.scriptQueue,
|
||||
this.researchQueue,
|
||||
this.analysisQueue,
|
||||
this.imageQueue,
|
||||
];
|
||||
|
||||
for (const queue of queues) {
|
||||
const job = await queue.getJob(jobId);
|
||||
if (job) return job;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private mapBullState(state: string): JobStatus {
|
||||
switch (state) {
|
||||
case 'completed':
|
||||
return JobStatus.COMPLETED;
|
||||
case 'failed':
|
||||
return JobStatus.FAILED;
|
||||
case 'active':
|
||||
return JobStatus.PROCESSING;
|
||||
default:
|
||||
return JobStatus.QUEUED;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user