generated from fahricansecer/boilerplate-be
486 lines
16 KiB
TypeScript
486 lines
16 KiB
TypeScript
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);
|
||
}
|
||
}
|