feat: add rmq publishing

This commit is contained in:
Sergey Krylov 2026-02-23 11:31:13 +03:00
parent 0d184020d9
commit 5d3e468bf6
12 changed files with 135 additions and 8 deletions

View File

@ -35,6 +35,8 @@
"@teacinema/common": "^1.0.0", "@teacinema/common": "^1.0.0",
"@teacinema/contracts": "^1.0.0", "@teacinema/contracts": "^1.0.0",
"@teacinema/passport": "^1.0.0", "@teacinema/passport": "^1.0.0",
"amqp-connection-manager": "^5.0.0",
"amqplib": "^0.10.9",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.3", "class-validator": "^0.14.3",
"dotenv-expand": "^12.0.3", "dotenv-expand": "^12.0.3",

View File

@ -6,8 +6,10 @@ import {
grpcEnv, grpcEnv,
passportEnv, passportEnv,
redisEnv, redisEnv,
rmqEnv,
telegramEnv telegramEnv
} from './config' } from './config'
import { MessagingModule } from './infra/messaging/messaging.module'
import { PrismaModule } from './infra/prisma/prisma.module' import { PrismaModule } from './infra/prisma/prisma.module'
import { RedisModule } from './infra/redis/redis.module' import { RedisModule } from './infra/redis/redis.module'
import { AccountModule } from './modules/account/account.module' import { AccountModule } from './modules/account/account.module'
@ -20,7 +22,7 @@ import { TokenModule } from './modules/token/token.module'
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
isGlobal: true, isGlobal: true,
load: [dabataseEnv, grpcEnv, redisEnv, passportEnv, telegramEnv] load: [dabataseEnv, grpcEnv, redisEnv, passportEnv, telegramEnv, rmqEnv]
}), }),
PrismaModule, PrismaModule,
RedisModule, RedisModule,
@ -28,7 +30,8 @@ import { TokenModule } from './modules/token/token.module'
OtpModule, OtpModule,
AccountModule, AccountModule,
TelegramModule, TelegramModule,
TokenModule TokenModule,
MessagingModule
] ]
}) })
export class AppModule {} export class AppModule {}

View File

@ -3,3 +3,4 @@ export * from './database.env'
export * from './redis.env' export * from './redis.env'
export * from './passport.env' export * from './passport.env'
export * from './telegram.env' export * from './telegram.env'
export * from './rmq.env'

14
src/config/env/rmq.env.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import { registerAs } from '@nestjs/config'
import { RmqValidator } from '@/config/validators'
import { validateEnv } from '@/shared/utils'
import { RmqConfig } from '../interfaces/rmq.interface'
export const rmqEnv = registerAs<RmqConfig>('rmq', () => {
validateEnv(process.env, RmqValidator)
return {
url: process.env.RMQ_URL
}
})

View File

@ -2,6 +2,7 @@ import { DatabaseConfig } from './database.interface'
import { GrpcConfig } from './grpc.interface' import { GrpcConfig } from './grpc.interface'
import { PassportConfig } from './passport.interface' import { PassportConfig } from './passport.interface'
import { RedisConfig } from './redis.interface' import { RedisConfig } from './redis.interface'
import { RmqConfig } from './rmq.interface'
import { TelegramConfig } from './telegram.interface' import { TelegramConfig } from './telegram.interface'
export interface AllConfigs { export interface AllConfigs {
@ -10,4 +11,5 @@ export interface AllConfigs {
redis: RedisConfig redis: RedisConfig
passport: PassportConfig passport: PassportConfig
telegram: TelegramConfig telegram: TelegramConfig
rmq: RmqConfig
} }

View File

@ -0,0 +1,3 @@
export interface RmqConfig {
url: string
}

View File

@ -3,3 +3,4 @@ export * from './database.validator'
export * from './redis.validator' export * from './redis.validator'
export * from './passport.validator' export * from './passport.validator'
export * from './telegram.validator' export * from './telegram.validator'
export * from './rmq.validator'

View File

@ -0,0 +1,6 @@
import { IsString } from 'class-validator'
export class RmqValidator {
@IsString()
public RMQ_URL: string
}

View File

@ -0,0 +1,32 @@
import { Global, Module } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { AllConfigs } from '@/config'
import { MessagingService } from './messaging.service'
@Global()
@Module({
imports: [
ClientsModule.registerAsync([
{
name: 'NOTIFICATION_CLIENT',
useFactory: (configService: ConfigService<AllConfigs>) => ({
transport: Transport.RMQ,
options: {
urls: [configService.get<string>('rmq.url', { infer: true })],
queue: 'notifications_queue',
queueOptions: {
durable: false
}
}
}),
inject: [ConfigService]
}
])
],
providers: [MessagingService],
exports: [MessagingService]
})
export class MessagingModule {}

View File

@ -0,0 +1,14 @@
import { Inject, Injectable } from '@nestjs/common'
import { ClientProxy } from '@nestjs/microservices'
import { OtpRequestedEvent } from '@teacinema/contracts'
@Injectable()
export class MessagingService {
constructor(
@Inject('NOTIFICATION_CLIENT') private readonly client: ClientProxy
) {}
public otpRequested(data: OtpRequestedEvent) {
return this.client.emit('auth.otp.requested', data)
}
}

View File

@ -11,6 +11,7 @@ import {
VerifyOtpResponse VerifyOtpResponse
} from '@teacinema/contracts/gen/auth' } from '@teacinema/contracts/gen/auth'
import { MessagingService } from '@/infra/messaging/messaging.service'
import { OtpService } from '@/modules/otp/otp.service' import { OtpService } from '@/modules/otp/otp.service'
import { UserRepository } from '@/shared/utils/repositories' import { UserRepository } from '@/shared/utils/repositories'
@ -21,7 +22,8 @@ export class AuthService {
public constructor( public constructor(
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly otpService: OtpService, private readonly otpService: OtpService,
private readonly tokenService: TokenService private readonly tokenService: TokenService,
private readonly messagingService: MessagingService
) {} ) {}
public async sendOtp(data: SendOtpRequest): Promise<SendOtpResponse> { public async sendOtp(data: SendOtpRequest): Promise<SendOtpResponse> {
@ -44,11 +46,15 @@ export class AuthService {
}) })
} }
const code = await this.otpService.send( const { code } = await this.otpService.send(
identifier, identifier,
type as 'email' | 'phone' type as 'email' | 'phone'
) )
console.debug('CODE', code) this.messagingService.otpRequested({
code: code.toString(),
identifier,
type: type as 'email' | 'phone'
})
return { ok: true } return { ok: true }
} }

View File

@ -1383,9 +1383,9 @@
integrity sha512-q3DURJbSk3k8MNWFIYaSM4LEcBgPbWa+HJmBz/nzYT4kuYitJVSXxpZ97kr0Ea+81AZwAU3JhQKlkK0SBWUi0A== integrity sha512-q3DURJbSk3k8MNWFIYaSM4LEcBgPbWa+HJmBz/nzYT4kuYitJVSXxpZ97kr0Ea+81AZwAU3JhQKlkK0SBWUi0A==
"@teacinema/contracts@^1.0.0": "@teacinema/contracts@^1.0.0":
version "1.0.9" version "1.1.0"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.9/contracts-1.0.9.tgz#14b136d0c08552147b0de637a7af6d340d538527" resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.1.0/contracts-1.1.0.tgz#0d2f03f283dc9ee048eeb770152127f2037b7133"
integrity sha512-9BwWvXaDKgRcef6+1ep9vUDWBdOndqheeTX5mXK5OOvLXbLOlzYoqAUoT0ZwaRwOCD+Je/LDrqcdti6eoss71Q== integrity sha512-x90R4R96AgrUDzOjUT7ePtc7phG3Gx61sI8/CZ1hETF3Ok/TWTHblxX/CIZVcOormBrxEh1FfU39P8/nfno91g==
dependencies: dependencies:
"@nestjs/microservices" "^11.1.12" "@nestjs/microservices" "^11.1.12"
protoc "33.4.0" protoc "33.4.0"
@ -2087,6 +2087,21 @@ ajv@^6.12.4, ajv@^6.12.5:
json-schema-traverse "^0.4.1" json-schema-traverse "^0.4.1"
uri-js "^4.2.2" uri-js "^4.2.2"
amqp-connection-manager@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/amqp-connection-manager/-/amqp-connection-manager-5.0.0.tgz#3bf72f76e1e041768080227fffab1f9baea70baa"
integrity sha512-88yQzqa5RSBgnLl504XjvCQJ7d+osskdwvg35Lwm1LRbfLjNU9p7SQUMSP82BB7mseiq9tIUPJ3HE3eXQbpjEw==
dependencies:
promise-breaker "^6.0.0"
amqplib@^0.10.9:
version "0.10.9"
resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.9.tgz#5b744c21d624f9307d0399e4d339b7354675831c"
integrity sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==
dependencies:
buffer-more-ints "~1.0.0"
url-parse "~1.5.10"
ansi-colors@4.1.3: ansi-colors@4.1.3:
version "4.1.3" version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@ -2332,6 +2347,11 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer-more-ints@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422"
integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==
buffer@^5.5.0: buffer@^5.5.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@ -4897,6 +4917,11 @@ prisma@^7.3.0:
mysql2 "3.15.3" mysql2 "3.15.3"
postgres "3.4.7" postgres "3.4.7"
promise-breaker@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/promise-breaker/-/promise-breaker-6.0.0.tgz#107d2b70f161236abdb4ac5a736c7eb8df489d0f"
integrity sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA==
proper-lockfile@4.1.2: proper-lockfile@4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
@ -4959,6 +4984,11 @@ qs@^6.14.0, qs@^6.14.1:
dependencies: dependencies:
side-channel "^1.1.0" side-channel "^1.1.0"
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
randombytes@^2.1.0: randombytes@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -5045,6 +5075,11 @@ require-from-string@^2.0.2:
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resolve-cwd@^3.0.0: resolve-cwd@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@ -5773,6 +5808,14 @@ uri-js@^4.2.2:
dependencies: dependencies:
punycode "^2.1.0" punycode "^2.1.0"
url-parse@~1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
util-deprecate@^1.0.1: util-deprecate@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"