Initial commit: restructure project with Docker Compose setup

This commit is contained in:
Admin
2026-05-26 20:16:01 +05:00
commit 47b15787f3
48 changed files with 6074 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MailModule } from './mail/mail.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
MailModule,
],
})
export class AppModule {}
@@ -0,0 +1,19 @@
import { IsString, IsOptional, IsEmail, MinLength } from 'class-validator';
export class SendApplicationDto {
@IsString()
@MinLength(1)
name: string;
@IsString()
@MinLength(1)
phone: string;
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsString()
message?: string;
}
+14
View File
@@ -0,0 +1,14 @@
import { Controller, Post, Body } from '@nestjs/common';
import { MailService } from './mail.service';
import { SendApplicationDto } from './dto/send-application.dto';
@Controller('api/mail')
export class MailController {
constructor(private readonly mailService: MailService) {}
@Post('application')
async sendApplication(@Body() dto: SendApplicationDto) {
await this.mailService.sendApplication(dto);
return { success: true, message: 'Application sent successfully' };
}
}
+9
View File
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { MailController } from './mail.controller';
@Module({
providers: [MailService],
controllers: [MailController],
})
export class MailModule {}
+84
View File
@@ -0,0 +1,84 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as nodemailer from 'nodemailer';
import { SendApplicationDto } from './dto/send-application.dto';
@Injectable()
export class MailService {
private transporter: nodemailer.Transporter;
constructor(private configService: ConfigService) {
this.transporter = nodemailer.createTransport({
host: this.configService.get('SMTP_HOST', 'smtp.yandex.ru'),
port: parseInt(this.configService.get('SMTP_PORT', '465'), 10),
secure: this.configService.get('SMTP_SECURE', 'true') === 'true',
auth: {
user: this.configService.getOrThrow('SMTP_USER'),
pass: this.configService.getOrThrow('SMTP_PASS'),
},
});
}
async sendApplication(dto: SendApplicationDto) {
const recipientEmail = this.configService.get('RECIPIENT_EMAIL') || this.configService.get('SMTP_USER');
const siteName = this.configService.get('SITE_NAME', 'Сайт психолога');
const html = `
<h2>Новая заявка с сайта «${siteName}»</h2>
<table style="border-collapse:collapse;width:100%;max-width:600px;font-family:sans-serif">
<tr style="border-bottom:1px solid #eee">
<td style="padding:10px 0;font-weight:bold;width:140px">Имя:</td>
<td style="padding:10px 0">${this.escapeHtml(dto.name)}</td>
</tr>
<tr style="border-bottom:1px solid #eee">
<td style="padding:10px 0;font-weight:bold">Телефон:</td>
<td style="padding:10px 0">${this.escapeHtml(dto.phone)}</td>
</tr>
${dto.email ? `
<tr style="border-bottom:1px solid #eee">
<td style="padding:10px 0;font-weight:bold">Email:</td>
<td style="padding:10px 0">${this.escapeHtml(dto.email)}</td>
</tr>
` : ''}
${dto.message ? `
<tr style="border-bottom:1px solid #eee">
<td style="padding:10px 0;font-weight:bold">Сообщение:</td>
<td style="padding:10px 0">${this.escapeHtml(dto.message).replace(/\n/g, '<br>')}</td>
</tr>
` : ''}
<tr>
<td style="padding:10px 0;font-weight:bold">Дата:</td>
<td style="padding:10px 0">${new Date().toLocaleString('ru-RU')}</td>
</tr>
</table>
`;
const text = `
Новая заявка с сайта «${siteName}»
Имя: ${dto.name}
Телефон: ${dto.phone}
${dto.email ? `Email: ${dto.email}\n` : ''}${dto.message ? `Сообщение: ${dto.message}\n` : ''}
Дата: ${new Date().toLocaleString('ru-RU')}
`.trim();
await this.transporter.sendMail({
from: `"${siteName}" <${this.configService.get('SMTP_USER')}>`,
to: recipientEmail,
subject: `Новая заявка от ${dto.name}`,
text,
html,
});
return { success: true };
}
private escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
}
+19
View File
@@ -0,0 +1,19 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: process.env.FRONTEND_URL || true,
credentials: true,
});
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
const port = process.env.PORT || 3001;
await app.listen(port);
console.log(`Server running on http://localhost:${port}`);
}
bootstrap();