main
Some checks failed
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-03-17 13:16:12 +03:00
parent 1a6b00478f
commit e0d41d0386
37 changed files with 1326 additions and 750 deletions

21
package-lock.json generated
View File

@@ -25,6 +25,7 @@
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^5.22.0",
"@types/uuid": "^10.0.0",
"bcrypt": "^6.0.0",
"bullmq": "^5.66.4",
"cache-manager": "^7.2.7",
@@ -43,6 +44,7 @@
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^13.0.0",
"zod": "^4.3.5"
},
"devDependencies": {
@@ -4942,6 +4944,12 @@
"@types/superagent": "^8.1.0"
}
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
"node_modules/@types/validator": {
"version": "13.15.10",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
@@ -12514,6 +12522,19 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

View File

@@ -35,6 +35,7 @@
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^5.22.0",
"@types/uuid": "^10.0.0",
"bcrypt": "^6.0.0",
"bullmq": "^5.66.4",
"cache-manager": "^7.2.7",
@@ -53,6 +54,7 @@
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^13.0.0",
"zod": "^4.3.5"
},
"devDependencies": {

View File

@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Module, Logger as NestLogger } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
@@ -41,6 +41,7 @@ import { AdminModule } from './modules/admin/admin.module';
import { HealthModule } from './modules/health/health.module';
import { GeminiModule } from './modules/gemini/gemini.module';
import { CmsModule } from './modules/cms/cms.module';
import { MailModule } from './modules/mail/mail.module';
// Guards
import {
@@ -71,7 +72,7 @@ import {
LoggerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
useFactory: (configService: ConfigService) => {
return {
pinoHttp: {
level: configService.get('app.isDevelopment') ? 'debug' : 'info',
@@ -121,6 +122,7 @@ import {
isGlobal: true,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
const logger = new NestLogger('CacheModule');
const useRedis = configService.get('REDIS_ENABLED', 'false') === 'true';
if (useRedis) {
@@ -132,18 +134,18 @@ import {
},
ttl: 60 * 1000, // 1 minute default
});
console.log('✅ Redis cache connected');
logger.log('✅ Redis cache connected');
return {
store: store as unknown as any,
ttl: 60 * 1000,
};
} catch {
console.warn('⚠️ Redis connection failed, using in-memory cache');
logger.warn('⚠️ Redis connection failed, using in-memory cache');
}
}
// Fallback to in-memory cache
console.log('📦 Using in-memory cache');
logger.log('📦 Using in-memory cache');
return {
ttl: 60 * 1000,
};
@@ -166,6 +168,9 @@ import {
// CMS Module
CmsModule,
// Mail Module
MailModule,
// Serve uploaded files
ServeStaticModule.forRoot({
rootPath: path.join(__dirname, '..', 'uploads'),
@@ -210,4 +215,10 @@ import {
},
],
})
export class AppModule { }
export class AppModule {
private readonly logger = new NestLogger(AppModule.name);
constructor() {
this.logger.log('AppModule initialized');
}
}

View File

@@ -80,7 +80,7 @@ export class GlobalExceptionFilter implements ExceptionFilter {
});
// Only update if translation exists (key is different from result)
if (translatedMessage !== `errors.${message}`) {
message = translatedMessage as string;
message = translatedMessage;
}
}
} catch {

View File

@@ -18,10 +18,12 @@ async function bootstrap() {
app.useGlobalInterceptors(new LoggerErrorInterceptor());
// Security Headers
app.use(helmet({
app.use(
helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
}));
}),
);
// Graceful Shutdown (Prisma & Docker)
app.enableShutdownHooks();

View File

@@ -10,9 +10,7 @@ import {
Query,
UseInterceptors,
UploadedFile,
ParseFilePipe,
MaxFileSizeValidator,
FileTypeValidator,
BadRequestException,
OnModuleInit,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@@ -136,15 +134,15 @@ export class CmsController implements OnModuleInit {
},
})
async uploadFile(
@UploadedFile(
new ParseFilePipe({
validators: [
new FileTypeValidator({ fileType: /image\/(jpeg|jpg|png|webp|gif|svg\+xml)/ }),
],
}),
)
file: Express.Multer.File,
@UploadedFile() file: Express.Multer.File,
) {
try {
if (!file) {
throw new BadRequestException('Dosya bulunamadı');
}
if (!file.mimetype.startsWith('image/')) {
throw new BadRequestException('Sadece görsel dosyaları yüklenebilir');
}
const url = `/uploads/${file.filename}`;
const media = await this.cmsService.createMediaFile({
filename: file.filename,
@@ -155,6 +153,9 @@ export class CmsController implements OnModuleInit {
size: file.size,
});
return media;
} catch (err) {
throw err;
}
}
@Public()

View File

@@ -9,4 +9,4 @@ import { DatabaseModule } from '../../database/database.module';
providers: [CmsService],
exports: [CmsService],
})
export class CmsModule { }
export class CmsModule {}

View File

@@ -1,4 +1,9 @@
import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common';
import {
Injectable,
Logger,
NotFoundException,
InternalServerErrorException,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { PrismaService } from '../../database/prisma.service';
import {
@@ -14,7 +19,7 @@ import {
export class CmsService {
private readonly logger = new Logger(CmsService.name);
constructor(private readonly prisma: PrismaService) { }
constructor(private readonly prisma: PrismaService) {}
// ── Audit Logging ──────────────────────────────
@@ -38,7 +43,10 @@ export class CmsService {
this.logger.log(`[AUDIT] ${action} ${entity}:${entityId}`);
} catch (err) {
// Audit failure should never break the main operation
this.logger.error(`[AUDIT] Failed to log ${action} ${entity}:${entityId}`, err.stack);
this.logger.error(
`[AUDIT] Failed to log ${action} ${entity}:${entityId}`,
err.stack,
);
}
}
@@ -49,9 +57,13 @@ export class CmsService {
id: string,
operation: string,
): Promise<T> {
const record = await (this.prisma as any)[model].findUnique({ where: { id } });
const record = await (this.prisma as any)[model].findUnique({
where: { id },
});
if (!record) {
this.logger.error(`[VERIFY] ${operation} failed — record ${model}:${id} not found after write`);
this.logger.error(
`[VERIFY] ${operation} failed — record ${model}:${id} not found after write`,
);
throw new InternalServerErrorException(
`Veritabanı yazma doğrulaması başarısız: ${model}:${id} yazıldıktan sonra bulunamadı`,
);
@@ -71,7 +83,8 @@ export class CmsService {
async findProjectById(id: string) {
const project = await this.prisma.project.findUnique({ where: { id } });
if (!project || project.deletedAt) throw new NotFoundException('Project not found');
if (!project || project.deletedAt)
throw new NotFoundException('Project not found');
return project;
}
@@ -94,7 +107,11 @@ export class CmsService {
});
// Write verification
const verified = await this.verifyWrite('project', created.id, 'CREATE Project');
const verified = await this.verifyWrite(
'project',
created.id,
'CREATE Project',
);
// Audit log
await this.audit('project', created.id, 'CREATE', null, verified);
@@ -105,7 +122,7 @@ export class CmsService {
async updateProject(id: string, dto: UpdateProjectDto) {
const before = await this.findProjectById(id); // throws if not found
const updated = await this.prisma.project.update({
await this.prisma.project.update({
where: { id },
data: {
...dto,
@@ -132,9 +149,13 @@ export class CmsService {
});
// Write verification — deletedAt'ın set edildiğini doğrula
const verified = await (this.prisma as any).project.findUnique({ where: { id } });
const verified = await (this.prisma as any).project.findUnique({
where: { id },
});
if (!verified || !verified.deletedAt) {
this.logger.error(`[VERIFY] Soft DELETE failed — project:${id} deletedAt is still null`);
this.logger.error(
`[VERIFY] Soft DELETE failed — project:${id} deletedAt is still null`,
);
throw new InternalServerErrorException('Silme doğrulaması başarısız');
}
@@ -147,9 +168,10 @@ export class CmsService {
async restoreProject(id: string) {
const project = await this.prisma.project.findUnique({ where: { id } });
if (!project) throw new NotFoundException('Project not found');
if (!project.deletedAt) throw new NotFoundException('Project is not deleted');
if (!project.deletedAt)
throw new NotFoundException('Project is not deleted');
const restored = await this.prisma.project.update({
await this.prisma.project.update({
where: { id },
data: { deletedAt: null },
});
@@ -292,14 +314,20 @@ export class CmsService {
{
title: 'Deadpool',
image: 'https://picsum.photos/seed/deadpool/1920/1080?blur=2',
roles: JSON.stringify(['Official Turkish Voice', 'Character Voice Acting']),
roles: JSON.stringify([
'Official Turkish Voice',
'Character Voice Acting',
]),
color: '#FF5733',
sortOrder: 0,
},
{
title: 'Spider-Man',
image: 'https://picsum.photos/seed/spiderman/1920/1080?blur=2',
roles: JSON.stringify(['Official Turkish Voice', 'Character Voice Acting']),
roles: JSON.stringify([
'Official Turkish Voice',
'Character Voice Acting',
]),
color: '#C70039',
sortOrder: 1,
},
@@ -384,7 +412,11 @@ export class CmsService {
});
// Write verification
const verified = await this.verifyWrite('client', created.id, 'CREATE Client');
const verified = await this.verifyWrite(
'client',
created.id,
'CREATE Client',
);
// Audit log
await this.audit('client', created.id, 'CREATE', null, verified);
@@ -393,11 +425,16 @@ export class CmsService {
}
async updateClient(id: string, dto: UpdateClientDto) {
const before = await this.prisma.client.findUniqueOrThrow({ where: { id } }).catch(() => {
const before = await this.prisma.client
.findUniqueOrThrow({ where: { id } })
.catch(() => {
throw new NotFoundException('Client not found');
});
const updated = await this.prisma.client.update({ where: { id }, data: dto });
await this.prisma.client.update({
where: { id },
data: dto,
});
// Write verification
const verified = await this.verifyWrite('client', id, 'UPDATE Client');

View File

@@ -20,7 +20,9 @@ export class CreateProjectDto {
@IsString()
image: string;
@ApiProperty({ example: ['Official Turkish Voice', 'Character Voice Acting'] })
@ApiProperty({
example: ['Official Turkish Voice', 'Character Voice Acting'],
})
@IsArray()
@IsString({ each: true })
roles: string[];

View File

@@ -0,0 +1,50 @@
import { Controller, Post, Body, Get } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { MailService, ContactMailData } from './mail.service';
import { Public } from '../../common/decorators';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
class SendContactDto {
@IsNotEmpty()
@IsString()
name: string;
@IsEmail()
email: string;
@IsNotEmpty()
@IsString()
type: string;
@IsNotEmpty()
@IsString()
details: string;
@IsNotEmpty()
@IsString()
captchaId: string;
@IsNotEmpty()
@IsString()
captchaAnswer: string;
}
@ApiTags('Mail')
@Controller('mail')
export class MailController {
constructor(private readonly mailService: MailService) { }
@Public()
@Post('send')
@ApiOperation({ summary: 'Send contact form message' })
async sendContact(@Body() dto: SendContactDto) {
return this.mailService.sendContactMail(dto);
}
@Public()
@Get('captcha')
@ApiOperation({ summary: 'Get a new math captcha' })
async getCaptcha() {
return this.mailService.generateCaptcha();
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { MailController } from './mail.controller';
import { CmsModule } from '../cms/cms.module';
@Module({
imports: [CmsModule],
providers: [MailService],
controllers: [MailController],
exports: [MailService],
})
export class MailModule { }

View File

@@ -0,0 +1,123 @@
import { Injectable, Logger, Inject, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import type { Cache } from 'cache-manager';
import * as nodemailer from 'nodemailer';
import { CmsService } from '../cms/cms.service';
import { v4 as uuidv4 } from 'uuid';
export interface ContactMailData {
name: string;
email: string;
type: string;
details: string;
captchaId: string;
captchaAnswer: string;
}
@Injectable()
export class MailService {
private readonly logger = new Logger(MailService.name);
private transporter: nodemailer.Transporter;
constructor(
private configService: ConfigService,
private cmsService: CmsService,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
) {
this.initTransporter();
}
async generateCaptcha() {
const num1 = Math.floor(Math.random() * 10) + 1;
const num2 = Math.floor(Math.random() * 10) + 1;
const answer = num1 + num2;
const id = uuidv4();
await this.cacheManager.set(`captcha:${id}`, answer.toString(), 300000); // 5 mins
return {
id,
question: `${num1} + ${num2} = ?`,
};
}
async verifyCaptcha(id: string, answer: string) {
const cachedAnswer = await this.cacheManager.get(`captcha:${id}`);
if (!cachedAnswer) {
throw new BadRequestException('Captcha expired or invalid');
}
if (String(cachedAnswer) !== String(answer).trim()) {
throw new BadRequestException('Incorrect captcha answer');
}
await this.cacheManager.del(`captcha:${id}`);
return true;
}
private initTransporter(customConfig?: any) {
this.transporter = nodemailer.createTransport({
host: customConfig?.host || this.configService.get<string>('MAIL_HOST'),
port: customConfig?.port || this.configService.get<number>('MAIL_PORT', 587),
secure: customConfig?.secure === 'true' || customConfig?.secure === true,
auth: {
user: customConfig?.user || this.configService.get<string>('MAIL_USER'),
pass: customConfig?.pass || this.configService.get<string>('MAIL_PASSWORD'),
},
});
}
async sendContactMail(data: ContactMailData) {
// Verify Captcha
await this.verifyCaptcha(data.captchaId, data.captchaAnswer);
const isMailEnabled = this.configService.get<boolean>('features.mail', false);
// Alıcı mailini veritabanından veya varsayılandan al
const settings = await this.cmsService.findContentBySection('mail_settings', 'tr');
const targetEmail = settings?.targetEmail || 'haruncanmedia@gmail.com';
if (!isMailEnabled) {
this.logger.warn('📧 Mail service is disabled. Message not sent, but logged:');
this.logger.log(`TO: ${targetEmail}, FROM: ${data.name} <${data.email}>, TYPE: ${data.type}, MESSAGE: ${data.details}`);
return { success: true, message: 'Mail dev-mode: logged to console' };
}
// Eğer veritabanında SMTP ayarları varsa transporter'ı güncelle
if (settings && settings.host && settings.user && settings.pass) {
this.logger.log('📧 Using dynamic SMTP settings from database');
this.initTransporter(settings);
}
try {
const info = await this.transporter.sendMail({
from: settings?.from || this.configService.get<string>('MAIL_FROM', '"HarunCAN Studio" <noreply@haruncan.com>'),
to: targetEmail,
subject: `New Contact Form Message from ${data.name}`,
text: `Name: ${data.name}\nEmail: ${data.email}\nInquiry Type: ${data.type}\nDetails: ${data.details}`,
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
<h2 style="color: #FF5733; border-bottom: 2px solid #FF5733; padding-bottom: 10px;">New Contact Form Submission</h2>
<p><strong>Name:</strong> ${data.name}</p>
<p><strong>Email:</strong> ${data.email}</p>
<p><strong>Inquiry Type:</strong> ${data.type}</p>
<div style="background: #f8fafc; padding: 15px; border-left: 4px solid #FF5733; margin-top: 20px;">
<p style="margin: 0; color: #475569;"><strong>Message Details:</strong></p>
<p style="margin-top: 10px; line-height: 1.6;">${data.details.replace(/\n/g, '<br>')}</p>
</div>
<p style="font-size: 12px; color: #94a3b8; margin-top: 30px; border-top: 1px solid #e2e8f0; padding-top: 10px;">
Sent from HarunCAN Studio Contact Form
</p>
</div>
`,
});
this.logger.log(`📧 Contact mail sent: ${info.messageId}`);
return { success: true, messageId: info.messageId };
} catch (error) {
this.logger.error('❌ Failed to send contact mail:', error);
throw error;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,105 @@
{
"name": "bbb",
"version": "0.0.1",
"description": "Generated by Antigravity CLI",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.964.0",
"@google/genai": "^1.35.0",
"@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.1.0",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.11",
"@nestjs/serve-static": "^5.0.4",
"@nestjs/swagger": "^11.2.4",
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^5.22.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",
"helmet": "^8.1.0",
"ioredis": "^5.9.0",
"nestjs-i18n": "^10.6.0",
"nestjs-pino": "^4.5.0",
"nodemailer": "^7.0.12",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pino": "^10.1.0",
"pino-http": "^11.0.0",
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"zod": "^4.3.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.0",
"@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",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"pino-pretty": "^13.1.3",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,105 @@
{
"name": "bbb",
"version": "0.0.1",
"description": "Generated by Antigravity CLI",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.964.0",
"@google/genai": "^1.35.0",
"@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.1.0",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.11",
"@nestjs/serve-static": "^5.0.4",
"@nestjs/swagger": "^11.2.4",
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^5.22.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",
"helmet": "^8.1.0",
"ioredis": "^5.9.0",
"nestjs-i18n": "^10.6.0",
"nestjs-pino": "^4.5.0",
"nodemailer": "^7.0.12",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pino": "^10.1.0",
"pino-http": "^11.0.0",
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"zod": "^4.3.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.0",
"@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",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"pino-pretty": "^13.1.3",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,105 @@
{
"name": "bbb",
"version": "0.0.1",
"description": "Generated by Antigravity CLI",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.964.0",
"@google/genai": "^1.35.0",
"@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.1.0",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.11",
"@nestjs/serve-static": "^5.0.4",
"@nestjs/swagger": "^11.2.4",
"@nestjs/terminus": "^11.0.0",
"@nestjs/throttler": "^6.5.0",
"@prisma/client": "^5.22.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",
"helmet": "^8.1.0",
"ioredis": "^5.9.0",
"nestjs-i18n": "^10.6.0",
"nestjs-pino": "^4.5.0",
"nodemailer": "^7.0.12",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pino": "^10.1.0",
"pino-http": "^11.0.0",
"prisma": "^5.22.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"zod": "^4.3.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/bcrypt": "^6.0.0",
"@types/express": "^5.0.0",
"@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",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"pino-pretty": "^13.1.3",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB