Files
ContentGen_BE/src/modules/projects/projects.controller.ts
T
Harun CAN a40619ef33
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled
main
2026-05-06 10:48:07 +02:00

486 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Controller,
Get,
Post,
Patch,
Delete,
Param,
Body,
Query,
HttpCode,
HttpStatus,
Logger,
ParseUUIDPipe,
Req,
UploadedFile,
UseInterceptors,
BadRequestException,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiQuery,
ApiConsumes,
} from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { ProjectsService } from './projects.service';
import {
CreateProjectDto,
UpdateProjectDto,
CreateFromTweetDto,
CreateFromYoutubeDto,
CreateFromDocumentDto,
CreateFromExtractedTextDto,
CreateFromTextDto,
} from './dto/project.dto';
@ApiTags('projects')
@ApiBearerAuth()
@Controller('projects')
export class ProjectsController {
private readonly logger = new Logger(ProjectsController.name);
constructor(private readonly projectsService: ProjectsService) {}
/**
* Yeni bir video projesi oluşturur (DRAFT durumunda).
*/
@Post()
@ApiOperation({ summary: 'Yeni video projesi oluştur' })
@ApiResponse({ status: 201, description: 'Proje başarıyla oluşturuldu' })
async create(@Body() dto: CreateProjectDto, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Yeni proje oluşturuluyor: "${dto.title}"`);
return this.projectsService.create(userId, dto);
}
/**
* Kullanıcının tüm projelerini listeler.
*/
@Get()
@ApiOperation({ summary: 'Tüm projeleri listele' })
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'status', required: false, type: String })
@ApiResponse({ status: 200, description: 'Proje listesi' })
async findAll(
@Req() req: any,
@Query('page') page?: number,
@Query('limit') limit?: number,
@Query('status') status?: string,
) {
const userId = req.user?.id || req.user?.sub;
return this.projectsService.findAll(userId, {
page: page || 1,
limit: limit || 10,
status,
});
}
/**
* Render kuyruğu genel görünümü — aktif, bekleyen ve son tamamlanan işler.
*/
@Get('render-queue')
@ApiOperation({ summary: 'Render kuyruğu genel görünümünü getir' })
@ApiResponse({ status: 200, description: 'Render kuyruk özeti' })
async getRenderQueue(@Req() req: any) {
const userId = req.user?.id || req.user?.sub;
return this.projectsService.getRenderQueue(userId);
}
/**
* Tek bir projeyi sahneleri ve medya asset'leriyle birlikte getirir.
*/
@Get(':id')
@ApiOperation({ summary: 'Proje detaylarını getir' })
@ApiResponse({ status: 200, description: 'Proje detayları' })
@ApiResponse({ status: 404, description: 'Proje bulunamadı' })
async findOne(@Param('id', ParseUUIDPipe) id: string, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
return this.projectsService.findOne(userId, id);
}
/**
* Projeyi günceller (sadece DRAFT durumundaki projeler güncellenebilir).
*/
@Patch(':id')
@ApiOperation({ summary: 'Projeyi güncelle' })
@ApiResponse({ status: 200, description: 'Proje güncellendi' })
async update(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: UpdateProjectDto,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Proje güncelleniyor: ${id}`);
return this.projectsService.update(userId, id, dto);
}
/**
* Projeyi soft-delete ile siler.
*/
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Projeyi sil (soft delete)' })
@ApiResponse({ status: 204, description: 'Proje silindi' })
async remove(@Param('id', ParseUUIDPipe) id: string, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Proje siliniyor: ${id}`);
return this.projectsService.remove(userId, id);
}
/**
* AI ile senaryo üretimini tetikler.
*/
@Post(':id/generate-script')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'AI ile senaryo üret (Gemini)' })
@ApiResponse({ status: 200, description: 'Senaryo üretildi ve kaydedildi' })
async generateScript(
@Param('id', ParseUUIDPipe) id: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Senaryo üretimi başlatılıyor: ${id}`);
return this.projectsService.generateScript(userId, id);
}
/**
* Senaryoyu onaylar ve video üretim kuyruğuna gönderir.
*/
@Post(':id/approve')
@HttpCode(HttpStatus.ACCEPTED)
@ApiOperation({ summary: 'Senaryoyu onayla ve video üretimini başlat' })
@ApiResponse({ status: 202, description: 'Video üretimi kuyruğa eklendi' })
async approveAndStartGeneration(
@Param('id', ParseUUIDPipe) id: string,
@Body() body: { ttsProvider?: string; visualEffect?: string },
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Proje onaylanıyor ve kuyruğa gönderiliyor: ${id}`);
return this.projectsService.approveAndQueueGeneration(userId, id, body?.ttsProvider, body?.visualEffect);
}
/**
* Aktif render işlemini iptal eder.
*/
@Post(':id/cancel-render')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Aktif render işlemini iptal et' })
@ApiResponse({ status: 200, description: 'Render işlemi iptal edildi' })
async cancelRender(@Param('id', ParseUUIDPipe) id: string, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Render iptal isteği: ${id}`);
return this.projectsService.cancelRenderJob(userId, id);
}
/**
* X/Twitter tweet URL'sinden otomatik proje oluşturur ve senaryo üretir.
* Tweet çekilir → prompt'a dönüştürülür → AI senaryo üretir → proje kaydedilir.
*/
@Post('from-tweet')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: "X/Twitter tweet'ten proje oluştur" })
@ApiResponse({
status: 201,
description: "Tweet'ten proje oluşturuldu ve senaryo üretildi",
})
@ApiResponse({
status: 400,
description: "Geçersiz tweet URL'si veya tweet bulunamadı",
})
async createFromTweet(@Body() dto: CreateFromTweetDto, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Tweet'ten proje oluşturuluyor: ${dto.tweetUrl}`);
return this.projectsService.createFromTweet(userId, dto);
}
/**
* YouTube URL'sinden otomatik proje oluşturur ve senaryo üretir.
*/
@Post('from-youtube')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'YouTube videosundan proje oluştur' })
@ApiResponse({
status: 201,
description: 'YouTube videosundan proje oluşturuldu ve senaryo üretildi',
})
@ApiResponse({
status: 400,
description: "Geçersiz YouTube URL'si veya video bulunamadı",
})
async createFromYoutube(@Body() dto: CreateFromYoutubeDto, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`YouTube'dan proje oluşturuluyor: ${dto.youtubeUrl}`);
return this.projectsService.createFromYoutube(userId, dto);
}
/**
* Serbest metin veya fikir üzerinden proje oluşturur.
*/
@Post('from-text')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Serbest metinden proje oluştur' })
@ApiResponse({
status: 201,
description: 'Metinden proje oluşturuldu ve senaryo üretildi',
})
async createFromText(@Body() dto: CreateFromTextDto, @Req() req: any) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Serbest metinden proje oluşturuluyor...`);
return this.projectsService.createFromText(userId, dto);
}
/**
* Yüklenen dokümandan (Word, PDF, Excel vb.) otomatik proje oluşturur.
*/
@Post('from-document')
@HttpCode(HttpStatus.CREATED)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'Dosyadan/Dokümandan proje oluştur' })
@ApiResponse({
status: 201,
description: 'Belgeden proje oluşturuldu ve senaryo üretildi',
})
async createFromDocument(
@UploadedFile() file: Express.Multer.File,
@Body() dto: CreateFromDocumentDto,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Dosyadan proje oluşturuluyor: ${file?.originalname}`);
if (!file) {
throw new BadRequestException('Dosya yüklenmedi');
}
return this.projectsService.createFromDocument(userId, file, dto);
}
/**
* Doküman yüklenip metni çıkarılır ve video konu önerileri üretilir.
*/
@Post('extract-document-topics')
@HttpCode(HttpStatus.OK)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'Dosyadan metin çıkar ve konu önerileri al' })
@ApiResponse({
status: 200,
description: 'Metin ve konular başarıyla çıkarıldı',
})
async extractDocumentTopics(@UploadedFile() file: Express.Multer.File) {
this.logger.log(
`Dosyadan metin ve konular çıkarılıyor: ${file?.originalname}`,
);
if (!file) {
throw new BadRequestException('Dosya yüklenmedi');
}
return this.projectsService.extractDocumentTopics(file);
}
/**
* Extracted text ve seçilen konu üzerinden doğrudan proje oluşturur.
*/
@Post('document-from-topic')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Seçilen konu ve metin ile proje oluştur' })
@ApiResponse({
status: 201,
description: 'Seçilen konu baz alınarak proje oluşturuldu',
})
async createFromTopic(
@Body() dto: CreateFromExtractedTextDto,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(
`Metin ve konu üzerinden proje oluşturuluyor. Konu: ${dto.topic}`,
);
return this.projectsService.createFromExtractedText(userId, dto);
}
/**
* Tekil sahne güncelleme (narrasyon, görsel prompt, süre).
*/
@Patch(':id/scenes/:sceneId')
@ApiOperation({ summary: 'Sahneyi güncelle' })
@ApiResponse({ status: 200, description: 'Sahne güncellendi' })
async updateScene(
@Param('id', ParseUUIDPipe) id: string,
@Param('sceneId', ParseUUIDPipe) sceneId: string,
@Body()
body: {
narrationText?: string;
visualPrompt?: string;
subtitleText?: string;
duration?: number;
},
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Sahne güncelleniyor: ${sceneId} (proje: ${id})`);
return this.projectsService.updateScene(userId, id, sceneId, body);
}
/**
* Tekil sahneyi AI ile yeniden üretir.
*/
@Post(':id/scenes/:sceneId/regenerate')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Sahneyi AI ile yeniden üret' })
@ApiResponse({ status: 200, description: 'Sahne yeniden üretildi' })
async regenerateScene(
@Param('id', ParseUUIDPipe) id: string,
@Param('sceneId', ParseUUIDPipe) sceneId: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Sahne yeniden üretiliyor: ${sceneId} (proje: ${id})`);
return this.projectsService.regenerateScene(userId, id, sceneId);
}
@Delete(':id/media/:mediaId')
@ApiOperation({ summary: 'Sahnede bulunan bir medyayı (MediaAsset) sil' })
@ApiResponse({ status: 200, description: 'Medya başarıyla silindi' })
async deleteMedia(
@Param('id', ParseUUIDPipe) id: string,
@Param('mediaId', ParseUUIDPipe) mediaId: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Medya silme isteği. Proje: ${id}, Medya: ${mediaId}`);
return this.projectsService.deleteSceneMedia(userId, id, mediaId);
}
/**
* Proje için 5 yeni SEO-optimized başlık üretir (Gemini AI).
*/
@Post(':id/generate-seo-titles')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'AI ile 5 yeni SEO başlığı üret' })
@ApiResponse({
status: 200,
description: 'SEO başlıkları başarıyla üretildi',
})
async generateSeoTitles(
@Param('id', ParseUUIDPipe) id: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`SEO başlık üretimi isteniyor: ${id}`);
return this.projectsService.generateSeoTitles(userId, id);
}
/**
* Proje için kapsamlı SEO ve Sosyal Medya içeriklerini yeniden üretir (Gemini AI).
*/
@Post(':id/generate-social-content')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'AI ile SEO ve Sosyal Medya içeriklerini üret' })
@ApiResponse({
status: 200,
description: 'İçerikler başarıyla üretildi',
})
async generateSocialContent(
@Param('id', ParseUUIDPipe) id: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`SEO ve Sosyal Medya üretim isteği: ${id}`);
return this.projectsService.generateSocialAndSeoContent(userId, id);
}
/**
* Alternatif SEO başlıklarından birini seçerek projenin ana başlığını günceller.
*/
@Patch(':id/select-title')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'SEO başlığı seç ve proje başlığını güncelle' })
@ApiResponse({ status: 200, description: 'Başlık başarıyla güncellendi' })
async selectSeoTitle(
@Param('id', ParseUUIDPipe) id: string,
@Body('title') title: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
if (!title) {
throw new BadRequestException('Başlık (title) belirtilmelidir.');
}
this.logger.log(`SEO başlık seçimi: ${id} — "${title}"`);
return this.projectsService.selectSeoTitle(userId, id, title);
}
/**
* Projeyi farklı bir dile çevirir.
*/
@Post(':id/translate')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({
summary: 'Projeyi farklı bir dile çevir ve kopyasını oluştur',
})
@ApiResponse({
status: 201,
description: 'Proje çevirisi başarıyla tamamlandı',
})
async translateProject(
@Param('id', ParseUUIDPipe) id: string,
@Body('targetLanguage') targetLanguage: string,
@Req() req: any,
) {
if (!targetLanguage) {
throw new BadRequestException(
'Hedef dil (targetLanguage) belirtilmelidir.',
);
}
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Proje çevirisi isteniyor: ${id} -> ${targetLanguage}`);
return this.projectsService.translateProject(userId, id, targetLanguage);
}
/**
* Sahne için ID bazında görsel üret (Gemini AI).
* Kullanıcı custom prompt sağlarsa, önce prompt güncellenir ardından resim üretilir.
*/
@Post(':id/scenes/:sceneId/generate-image')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Sahne görselini üret' })
@ApiResponse({ status: 200, description: 'Görsel üretildi' })
async generateSceneImage(
@Param('id', ParseUUIDPipe) id: string,
@Param('sceneId', ParseUUIDPipe) sceneId: string,
@Body() body: { customPrompt?: string },
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(`Sahne görseli üretiliyor: ${sceneId} (proje: ${id})`);
return this.projectsService.generateSceneImage(
userId,
id,
sceneId,
body?.customPrompt,
);
}
/**
* Sahne görselini 4K olarak yeniden boyutlandırır (Upscale)
*/
@Post(':id/scenes/:sceneId/upscale-image')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Sahne görselini upscale (4K) yap' })
@ApiResponse({ status: 200, description: 'Görsel başarıyla upscale edildi' })
async upscaleSceneImage(
@Param('id', ParseUUIDPipe) id: string,
@Param('sceneId', ParseUUIDPipe) sceneId: string,
@Req() req: any,
) {
const userId = req.user?.id || req.user?.sub;
this.logger.log(
`Sahne görseli upscale ediliyor: ${sceneId} (proje: ${id})`,
);
return this.projectsService.upscaleSceneImage(userId, id, sceneId);
}
}