Интеграция с sms.ru

This commit is contained in:
Sergey Bolshakov 2026-03-10 21:27:17 +03:00
parent 053bd699f3
commit 1461d16983
3 changed files with 92 additions and 4 deletions

View File

@ -1,4 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { YachtsModule } from './yachts/yachts.module'; import { YachtsModule } from './yachts/yachts.module';
@ -10,7 +11,16 @@ import { ReviewsModule } from './reviews/reviews.module';
import { ReservationsModule } from './reservations/reservations.module'; import { ReservationsModule } from './reservations/reservations.module';
@Module({ @Module({
imports: [YachtsModule, CatalogModule, AuthModule, UsersModule, FilesModule, ReviewsModule, ReservationsModule], imports: [
ConfigModule.forRoot({ isGlobal: true }),
YachtsModule,
CatalogModule,
AuthModule,
UsersModule,
FilesModule,
ReviewsModule,
ReservationsModule,
],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
}) })

View File

@ -1,4 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthController } from './auth.controller'; import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module'; import { UsersModule } from '../users/users.module';
@ -10,6 +11,7 @@ import { RefreshTokenStoreService } from './refresh-token-store.service';
@Module({ @Module({
imports: [ imports: [
ConfigModule,
UsersModule, UsersModule,
JwtModule.register({ JwtModule.register({
secret: jwtConstants.secret, secret: jwtConstants.secret,

View File

@ -1,7 +1,26 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
export const SMS_SERVICE = 'SmsService'; export const SMS_SERVICE = 'SmsService';
const SMS_RU_SEND_URL = 'https://sms.ru/sms/send';
/** Ответ API sms.ru (фрагмент для одного номера). */
interface SmsRuSmsItem {
status: string;
status_code: number;
sms_id?: string;
status_text?: string;
}
/** Ответ API sms.ru при json=1. */
interface SmsRuResponse {
status: string;
status_code: number;
sms?: Record<string, SmsRuSmsItem>;
balance?: number;
}
/** Интерфейс для отправки SMS. В проде подставьте реализацию (SMS.ru, Twilio и т.д.). */ /** Интерфейс для отправки SMS. В проде подставьте реализацию (SMS.ru, Twilio и т.д.). */
export interface ISmsSender { export interface ISmsSender {
sendVerificationCode(phone: string, code: string): Promise<void>; sendVerificationCode(phone: string, code: string): Promise<void>;
@ -10,10 +29,67 @@ export interface ISmsSender {
@Injectable() @Injectable()
export class SmsService implements ISmsSender { export class SmsService implements ISmsSender {
private readonly logger = new Logger(SmsService.name); private readonly logger = new Logger(SmsService.name);
private readonly apiId: string | undefined;
private readonly isDev: boolean;
constructor(private readonly configService: ConfigService) {
this.apiId = this.configService.get<string>('SMS_RU_API_ID');
this.isDev = this.configService.get<string>('NODE_ENV') === 'development';
if (!this.apiId && !this.isDev) {
this.logger.warn(
'SMS_RU_API_ID не задан — SMS не отправляются, код верификации только логируется',
);
}
}
async sendVerificationCode(phone: string, code: string): Promise<void> { async sendVerificationCode(phone: string, code: string): Promise<void> {
// Заглушка: в разработке только логируем. Подключите реальный провайдер SMS. if (this.isDev) {
this.logger.log(`[SMS] Код для ${phone}: ${code}`); this.logger.log(`[dev] Код верификации для ${phone}: ${code}`);
// await this.smsProvider.send(phone, `Ваш код: ${code}`); return;
}
const msg = `Ваш код: ${code}`;
const to = this.toSmsRuPhone(phone);
if (!this.apiId) {
this.logger.log(`[SMS stub] Код для ${phone}: ${code}`);
return;
}
const url = new URL(SMS_RU_SEND_URL);
url.searchParams.set('api_id', this.apiId);
url.searchParams.set('to', to);
url.searchParams.set('msg', msg);
url.searchParams.set('json', '1');
try {
const res = await fetch(url.toString());
const data = (await res.json()) as SmsRuResponse;
if (data.status !== 'OK' || data.status_code !== 100) {
this.logger.error(`SMS.ru: ошибка запроса status=${data.status} status_code=${data.status_code}`);
throw new Error(`SMS.ru: ${data.status_code}`);
}
const item = data.sms?.[to];
if (item?.status !== 'OK') {
const text = item?.status_text ?? 'неизвестная ошибка';
this.logger.error(`SMS.ru: не удалось отправить на ${to}: ${text}`);
throw new Error(`SMS.ru: ${text}`);
}
this.logger.log(`SMS отправлен на ${phone}, код=${code}, sms_id=${item.sms_id ?? '—'}`);
} catch (err) {
this.logger.error(`Ошибка отправки SMS на ${phone}:`, err);
throw err;
}
}
/** Номер в формате sms.ru: только цифры, например 79255070602. */
private toSmsRuPhone(phone: string): string {
const digits = phone.replace(/\D/g, '');
if (digits.length === 10 && digits.startsWith('9')) return '7' + digits;
if (digits.length === 11 && digits.startsWith('7')) return digits;
return digits;
} }
} }