This commit is contained in:
Harun CAN
2026-03-23 03:15:08 +03:00
parent e60b6ea526
commit fd2580b311
24 changed files with 2409 additions and 4 deletions

View 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;
}
}
}