generated from fahricansecer/boilerplate-be
+2
-1
@@ -35,4 +35,5 @@ junit.xml
|
||||
|
||||
dist
|
||||
|
||||
cli-tool
|
||||
cli-tool
|
||||
.pnpm-store
|
||||
@@ -13,8 +13,10 @@ RUN dotnet publish -c Release -o /app/publish --no-restore
|
||||
FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# FFmpeg kurulumu (ARM64 native Alpine paketi)
|
||||
RUN apk add --no-cache ffmpeg font-dejavu
|
||||
# FFmpeg ve Globalization kurulumu (ARM64 native Alpine paketi)
|
||||
RUN apk add --no-cache ffmpeg font-dejavu icu-libs
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
|
||||
# Temp dizin oluştur
|
||||
RUN mkdir -p /tmp/contgen-render
|
||||
|
||||
@@ -33,9 +33,7 @@ public class S3StorageService
|
||||
var config = new AmazonS3Config
|
||||
{
|
||||
ServiceURL = _settings.Endpoint,
|
||||
ForcePathStyle = true,
|
||||
RequestChecksumCalculation = RequestChecksumCalculation.WHEN_REQUIRED,
|
||||
ResponseChecksumValidation = ResponseChecksumValidation.WHEN_REQUIRED,
|
||||
ForcePathStyle = true
|
||||
};
|
||||
|
||||
_s3Client = new AmazonS3Client(
|
||||
|
||||
@@ -39,12 +39,15 @@
|
||||
"@nestjs/websockets": "^11.1.17",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"axios": "^1.15.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bullmq": "^5.66.4",
|
||||
"cache-manager": "^7.2.7",
|
||||
"cache-manager-redis-yet": "^5.1.5",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.3",
|
||||
"express": "^5.2.1",
|
||||
"form-data": "^4.0.5",
|
||||
"helmet": "^8.1.0",
|
||||
"ioredis": "^5.9.0",
|
||||
"nestjs-i18n": "^10.6.0",
|
||||
@@ -70,7 +73,9 @@
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/form-data": "^2.5.2",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/multer": "^2.1.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/nodemailer": "^7.0.4",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
|
||||
Generated
+58
@@ -59,6 +59,9 @@ importers:
|
||||
'@types/sharp':
|
||||
specifier: ^0.32.0
|
||||
version: 0.32.0
|
||||
axios:
|
||||
specifier: ^1.15.0
|
||||
version: 1.15.0
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@@ -77,6 +80,12 @@ importers:
|
||||
class-validator:
|
||||
specifier: ^0.14.3
|
||||
version: 0.14.4
|
||||
express:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
form-data:
|
||||
specifier: ^4.0.5
|
||||
version: 4.0.5
|
||||
helmet:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
@@ -147,9 +156,15 @@ importers:
|
||||
'@types/express':
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.6
|
||||
'@types/form-data':
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2
|
||||
'@types/jest':
|
||||
specifier: ^30.0.0
|
||||
version: 30.0.0
|
||||
'@types/multer':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
'@types/node':
|
||||
specifier: ^22.10.7
|
||||
version: 22.19.15
|
||||
@@ -1721,6 +1736,10 @@ packages:
|
||||
'@types/express@5.0.6':
|
||||
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
|
||||
|
||||
'@types/form-data@2.5.2':
|
||||
resolution: {integrity: sha512-tfmcyHn1Pp9YHAO5r40+UuZUPAZbUEgqTel3EuEKpmF9hPkXgR4l41853raliXnb4gwyPNoQOfvgGGlHN5WSog==}
|
||||
deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/http-errors@2.0.5':
|
||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||
|
||||
@@ -1748,6 +1767,9 @@ packages:
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
'@types/multer@2.1.0':
|
||||
resolution: {integrity: sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==}
|
||||
|
||||
'@types/node@22.19.15':
|
||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||
|
||||
@@ -2153,6 +2175,9 @@ packages:
|
||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
axios@1.15.0:
|
||||
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||
|
||||
babel-jest@30.3.0:
|
||||
resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -2809,6 +2834,15 @@ packages:
|
||||
flatted@3.4.2:
|
||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||
|
||||
follow-redirects@1.15.11:
|
||||
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -3780,6 +3814,10 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
pump@3.0.4:
|
||||
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||
|
||||
@@ -6449,6 +6487,10 @@ snapshots:
|
||||
'@types/express-serve-static-core': 5.1.1
|
||||
'@types/serve-static': 2.2.0
|
||||
|
||||
'@types/form-data@2.5.2':
|
||||
dependencies:
|
||||
form-data: 4.0.5
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
'@types/istanbul-lib-coverage@2.0.6': {}
|
||||
@@ -6477,6 +6519,10 @@ snapshots:
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/multer@2.1.0':
|
||||
dependencies:
|
||||
'@types/express': 5.0.6
|
||||
|
||||
'@types/node@22.19.15':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -6889,6 +6935,14 @@ snapshots:
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
|
||||
axios@1.15.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
babel-jest@30.3.0(@babel/core@7.29.0):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@@ -7601,6 +7655,8 @@ snapshots:
|
||||
|
||||
flatted@3.4.2: {}
|
||||
|
||||
follow-redirects@1.15.11: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
@@ -8753,6 +8809,8 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
pump@3.0.4:
|
||||
dependencies:
|
||||
end-of-stream: 1.4.5
|
||||
|
||||
@@ -236,6 +236,7 @@ enum SourceType {
|
||||
MANUAL
|
||||
X_TWEET
|
||||
YOUTUBE
|
||||
DOCUMENT
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ import {
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
fallbackLanguage: configService.get('i18n.fallbackLanguage', 'en'),
|
||||
loaderOptions: {
|
||||
path: path.join(__dirname, '..', 'i18n'),
|
||||
path: path.join(__dirname, 'i18n'),
|
||||
watch: configService.get('app.isDevelopment', true),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -242,6 +242,11 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
|
||||
/**
|
||||
* Gemini Image Generation API ile görsel üret.
|
||||
* 3 katmanlı fallback mimarisi:
|
||||
* 1) gemini-2.5-flash-image (Nano Banana — hızlı, stabil)
|
||||
* 2) gemini-3.1-flash-image-preview (Nano Banana 2 — en yeni)
|
||||
* 3) Imagen 4 Fast (generateImages API)
|
||||
*
|
||||
* Raspberry Pi 5 bellek koruması için buffer olarak döner.
|
||||
*
|
||||
* @param prompt - İngilizce görsel açıklaması
|
||||
@@ -256,63 +261,66 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
throw new Error('Gemini AI is not available. Check your configuration.');
|
||||
}
|
||||
|
||||
const imageModel = this.configService.get<string>(
|
||||
'gemini.imageModel',
|
||||
'gemini-2.0-flash-preview-image-generation',
|
||||
);
|
||||
// Güncel model sıralaması (Nisan 2026):
|
||||
// - gemini-2.5-flash-image: Nano Banana — stabil, hızlı
|
||||
// - gemini-3.1-flash-image-preview: Nano Banana 2 — en yeni, yüksek kalite
|
||||
const primaryModel = 'gemini-2.5-flash-image';
|
||||
const fallbackModel = 'gemini-3.1-flash-image-preview';
|
||||
|
||||
try {
|
||||
this.logger.debug(`🎨 Görsel üretiliyor: "${prompt.substring(0, 80)}..." [${aspectRatio}]`);
|
||||
|
||||
const enhancedPrompt = `Generate a high-quality image for this description: ${prompt}. Style: photorealistic, cinematic lighting, detailed. Aspect ratio: ${aspectRatio}.`;
|
||||
const enhancedPrompt = `Generate a high-quality image for this description: ${prompt}. Style: photorealistic, cinematic lighting, detailed. Aspect ratio: ${aspectRatio}. IMPORTANT: Generate only the image, no text response needed.`;
|
||||
|
||||
// 1) First try the stable Imagen-4 Fast API
|
||||
// ── Katman 1: gemini-2.5-flash-image (Nano Banana) ──
|
||||
try {
|
||||
this.logger.debug(`🔄 Katman 1: ${primaryModel} deneniyor...`);
|
||||
const result = await this.tryGenerateContentImage(primaryModel, enhancedPrompt);
|
||||
if (result) {
|
||||
this.logger.log(`✅ Görsel üretildi (${primaryModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
|
||||
return result;
|
||||
}
|
||||
} catch (err1: any) {
|
||||
this.logger.warn(`⚠️ ${primaryModel} başarısız: ${err1.message?.substring(0, 120)}`);
|
||||
}
|
||||
|
||||
// ── Katman 2: gemini-3.1-flash-image-preview (Nano Banana 2) ──
|
||||
try {
|
||||
this.logger.debug(`🔄 Katman 2: ${fallbackModel} deneniyor...`);
|
||||
const result = await this.tryGenerateContentImage(fallbackModel, enhancedPrompt);
|
||||
if (result) {
|
||||
this.logger.log(`✅ Görsel üretildi (${fallbackModel}): ${(result.buffer.length / 1024).toFixed(1)} KB`);
|
||||
return result;
|
||||
}
|
||||
} catch (err2: any) {
|
||||
this.logger.warn(`⚠️ ${fallbackModel} başarısız: ${err2.message?.substring(0, 120)}`);
|
||||
}
|
||||
|
||||
// ── Katman 3: Imagen 4 Fast (generateImages API) ──
|
||||
try {
|
||||
this.logger.debug(`🔄 Katman 3: Imagen 4 Fast deneniyor...`);
|
||||
const response = await this.client!.models.generateImages({
|
||||
model: 'imagen-4.0-fast-generate-001',
|
||||
prompt: enhancedPrompt,
|
||||
config: {
|
||||
numberOfImages: 1,
|
||||
aspectRatio: aspectRatio,
|
||||
outputMimeType: 'image/jpeg',
|
||||
personGeneration: 'ALLOW_ALL' as any
|
||||
}
|
||||
numberOfImages: 1,
|
||||
aspectRatio: aspectRatio,
|
||||
outputMimeType: 'image/jpeg',
|
||||
personGeneration: 'ALLOW_ALL' as any,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (response.generatedImages?.[0]?.image?.imageBytes) {
|
||||
const buffer = Buffer.from(response.generatedImages[0].image.imageBytes, 'base64');
|
||||
const mimeType = 'image/jpeg';
|
||||
this.logger.log(`✅ Görsel üretildi (Imagen): ${(buffer.length / 1024).toFixed(1)} KB [${mimeType}]`);
|
||||
this.logger.log(`✅ Görsel üretildi (Imagen 4): ${(buffer.length / 1024).toFixed(1)} KB`);
|
||||
return { buffer, mimeType };
|
||||
}
|
||||
} catch (imagenError: any) {
|
||||
this.logger.warn(`Imagen API error, falling back to generateContent... ${imagenError.message}`);
|
||||
} catch (err3: any) {
|
||||
this.logger.warn(`⚠️ Imagen 4 başarısız: ${err3.message?.substring(0, 120)}`);
|
||||
}
|
||||
|
||||
// 2) Fallback to Gemini Flash image modalities (experimental feature)
|
||||
const response = await this.client!.models.generateContent({
|
||||
model: imageModel,
|
||||
contents: enhancedPrompt,
|
||||
config: {
|
||||
responseModalities: ['IMAGE', 'TEXT'],
|
||||
},
|
||||
});
|
||||
|
||||
// Gemini image generation modeli, inlineData olarak görsel döner
|
||||
const candidate = response.candidates?.[0];
|
||||
const imagePart = candidate?.content?.parts?.find(
|
||||
(p: any) => p.inlineData?.mimeType?.startsWith('image/'),
|
||||
);
|
||||
|
||||
if (imagePart?.inlineData?.data) {
|
||||
const buffer = Buffer.from(imagePart.inlineData.data, 'base64');
|
||||
const mimeType = imagePart.inlineData.mimeType || 'image/png';
|
||||
|
||||
this.logger.log(`✅ Görsel üretildi (Flash): ${(buffer.length / 1024).toFixed(1)} KB [${mimeType}]`);
|
||||
return { buffer, mimeType };
|
||||
}
|
||||
|
||||
this.logger.warn('Gemini görsel üretemedi — response içinde image part bulunamadı');
|
||||
this.logger.error('❌ Tüm görsel üretim katmanları başarısız oldu');
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logger.error(`Gemini görsel üretim hatası: ${error instanceof Error ? error.message : error}`);
|
||||
@@ -320,6 +328,36 @@ IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateContent API ile görsel üretim denemesi.
|
||||
* responseModalities: ['IMAGE', 'TEXT'] kullanarak inlineData içinden resim çıkarır.
|
||||
*/
|
||||
private async tryGenerateContentImage(
|
||||
model: string,
|
||||
prompt: string,
|
||||
): Promise<{ buffer: Buffer; mimeType: string } | null> {
|
||||
const response = await this.client!.models.generateContent({
|
||||
model,
|
||||
contents: prompt,
|
||||
config: {
|
||||
responseModalities: ['IMAGE', 'TEXT'],
|
||||
},
|
||||
});
|
||||
|
||||
const candidate = response.candidates?.[0];
|
||||
const imagePart = candidate?.content?.parts?.find(
|
||||
(p: any) => p.inlineData?.mimeType?.startsWith('image/'),
|
||||
);
|
||||
|
||||
if (imagePart?.inlineData?.data) {
|
||||
const buffer = Buffer.from(imagePart.inlineData.data, 'base64');
|
||||
const mimeType = imagePart.inlineData.mimeType || 'image/png';
|
||||
return { buffer, mimeType };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sahne bazlı görsel üret — visualPrompt ve video stili kullanarak.
|
||||
*
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ApiResponse,
|
||||
createSuccessResponse,
|
||||
} from '../../common/types/api-response.type';
|
||||
import { User } from '@prisma/client/wasm';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { UserResponseDto } from './dto/user.dto';
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as bcrypt from 'bcrypt';
|
||||
import { PrismaService } from '../../database/prisma.service';
|
||||
import { BaseService } from '../../common/base';
|
||||
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
|
||||
import { User } from '@prisma/client/wasm';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService extends BaseService<
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const db = new PrismaClient();
|
||||
async function run() {
|
||||
const adminUser = await db.user.findUnique({ where: { email: 'admin@contentgen.ai' } });
|
||||
console.log("Admin ID:", adminUser?.id);
|
||||
|
||||
const projects = await db.project.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5,
|
||||
select: { id: true, title: true, createdAt: true, userId: true }
|
||||
});
|
||||
console.log("Last 5 projects:", projects);
|
||||
await db.$disconnect();
|
||||
}
|
||||
run();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "prisma"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user