refactor: refactor grpc service call

This commit is contained in:
Sergey Krylov 2026-03-08 11:17:18 +03:00
parent a85af3eb0c
commit f0e3c464d5
12 changed files with 134 additions and 188 deletions

View File

@ -27,7 +27,7 @@ export class AccountController {
@Body() dto: InitEmailChangeRequestDto, @Body() dto: InitEmailChangeRequestDto,
@CurrentUser() userId: string @CurrentUser() userId: string
) { ) {
return this.client.initEmailChange({ ...dto, userId }) return this.client.call('initEmailChange', { email: dto.email, userId })
} }
@ApiOperation({ @ApiOperation({
@ -42,7 +42,7 @@ export class AccountController {
@Body() dto: ConfirmEmailChangeRequestDto, @Body() dto: ConfirmEmailChangeRequestDto,
@CurrentUser() userId: string @CurrentUser() userId: string
) { ) {
return this.client.confirmEmailChange({ ...dto, userId }) return this.client.call('confirmEmailChange', { ...dto, userId })
} }
@ApiOperation({ @ApiOperation({
@ -57,7 +57,7 @@ export class AccountController {
@Body() dto: InitPhoneChangeRequestDto, @Body() dto: InitPhoneChangeRequestDto,
@CurrentUser() userId: string @CurrentUser() userId: string
) { ) {
return this.client.initPhoneChange({ ...dto, userId }) return this.client.call('initPhoneChange', { ...dto, userId })
} }
@ApiOperation({ @ApiOperation({
@ -72,6 +72,6 @@ export class AccountController {
@Body() dto: ConfirmPhoneChangeRequestDto, @Body() dto: ConfirmPhoneChangeRequestDto,
@CurrentUser() userId: string @CurrentUser() userId: string
) { ) {
return this.client.confirmPhoneChange({ ...dto, userId }) return this.client.call('confirmPhoneChange', { ...dto, userId })
} }
} }

View File

@ -1,42 +1,13 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices' import type { ClientGrpc } from '@nestjs/microservices'
import { import { InjectGrpc } from '@teacinema/common'
AccountServiceClient, import { AccountServiceClient } from '@teacinema/contracts/gen/account'
ConfirmEmailChangeRequest,
ConfirmPhoneChangeRequest, import { AbstractGrpcClient } from '../../shared/grpc/abstract-grpc.client'
GetAccountRequest,
InitEmailChangeRequest,
InitPhoneChangeRequest,
} from '@teacinema/contracts/gen/account'
@Injectable() @Injectable()
export class AccountClientGrpc implements OnModuleInit { export class AccountClientGrpc extends AbstractGrpcClient<AccountServiceClient> {
private accountService: AccountServiceClient constructor(@InjectGrpc('ACCOUNT_PACKAGE') client: ClientGrpc) {
super(client, 'AccountService')
constructor(@Inject('ACCOUNT_PACKAGE') private readonly client: ClientGrpc) {}
onModuleInit() {
this.accountService =
this.client.getService<AccountServiceClient>('AccountService')
}
public getAccount(request: GetAccountRequest) {
return this.accountService.getAccount(request)
}
public initEmailChange(request: InitEmailChangeRequest) {
return this.accountService.initEmailChange(request)
}
public initPhoneChange(request: InitPhoneChangeRequest) {
return this.accountService.initPhoneChange(request)
}
public confirmEmailChange(request: ConfirmEmailChangeRequest) {
return this.accountService.confirmEmailChange(request)
}
public confirmPhoneChange(request: ConfirmPhoneChangeRequest) {
return this.accountService.confirmPhoneChange(request)
} }
} }

View File

@ -1,28 +1,11 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { ConfigService } from '@nestjs/config' import { GrpcModule } from '@teacinema/common'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { PROTO_PATHS } from '@teacinema/contracts'
import { AccountController } from './account.controller' import { AccountController } from './account.controller'
import { AccountClientGrpc } from './account.grpc' import { AccountClientGrpc } from './account.grpc'
@Module({ @Module({
imports: [ imports: [GrpcModule.register(['ACCOUNT_PACKAGE'])],
ClientsModule.registerAsync([
{
name: 'ACCOUNT_PACKAGE',
useFactory: (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'account.v1',
protoPath: PROTO_PATHS.ACCOUNT,
url: configService.getOrThrow<string>('AUTH_GRPC_URL')
}
}),
inject: [ConfigService]
}
])
],
controllers: [AccountController], controllers: [AccountController],
providers: [AccountClientGrpc], providers: [AccountClientGrpc],
exports: [AccountClientGrpc] exports: [AccountClientGrpc]

View File

@ -12,7 +12,6 @@ import {
import { ConfigService } from '@nestjs/config' import { ConfigService } from '@nestjs/config'
import { ApiOperation } from '@nestjs/swagger' import { ApiOperation } from '@nestjs/swagger'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { lastValueFrom } from 'rxjs'
import { AuthClientGrpc } from './auth.grpc' import { AuthClientGrpc } from './auth.grpc'
import { import {
@ -36,7 +35,7 @@ export class AuthController {
@Post('otp/send') @Post('otp/send')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
public sendOtp(@Body() dto: SendOtpRequestDto) { public sendOtp(@Body() dto: SendOtpRequestDto) {
return this.client.sendOtp(dto) return this.client.call('sendOtp', dto)
} }
@ApiOperation({ @ApiOperation({
@ -49,8 +48,9 @@ export class AuthController {
@Body() dto: VerifyOtpRequestDto, @Body() dto: VerifyOtpRequestDto,
@Res({ passthrough: true }) res: Response @Res({ passthrough: true }) res: Response
) { ) {
const { accessToken, refreshToken } = await lastValueFrom( const { accessToken, refreshToken } = await this.client.call(
this.client.verifyOtp(dto) 'verifyOtp',
dto
) )
res.cookie('refreshToken', refreshToken, { res.cookie('refreshToken', refreshToken, {
@ -75,10 +75,8 @@ export class AuthController {
@Res({ passthrough: true }) res: Response @Res({ passthrough: true }) res: Response
) { ) {
const refreshToken = req.cookies?.refreshToken as string const refreshToken = req.cookies?.refreshToken as string
const { accessToken, refreshToken: newRefreshToken } =
const { accessToken, refreshToken: newRefreshToken } = await lastValueFrom( await this.client.call('refresh', { refreshToken })
this.client.refresh({ refreshToken })
)
res.cookie('refreshToken', newRefreshToken, { res.cookie('refreshToken', newRefreshToken, {
httpOnly: true, httpOnly: true,
@ -105,7 +103,7 @@ export class AuthController {
@Get('telegram/init') @Get('telegram/init')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
public telegramInit() { public telegramInit() {
return this.client.telegramInit() return this.client.call('telegramInit', {})
} }
@Post('telegram/verify') @Post('telegram/verify')
@ -117,7 +115,7 @@ export class AuthController {
const query = JSON.parse(atob(dto.tgAuthResult)) as { const query = JSON.parse(atob(dto.tgAuthResult)) as {
[key: string]: string [key: string]: string
} }
const result = await lastValueFrom(this.client.telegramVerify({ query })) const result = await this.client.call('telegramVerify', { query })
if (result.url) { if (result.url) {
return result return result
@ -146,10 +144,9 @@ export class AuthController {
@Body() dto: TelegramFinalyzeRequestDto, @Body() dto: TelegramFinalyzeRequestDto,
@Res({ passthrough: true }) res: Response @Res({ passthrough: true }) res: Response
) { ) {
const { sessionId } = dto const { accessToken, refreshToken } = await this.client.call(
'telegramConsume',
const { accessToken, refreshToken } = await lastValueFrom( dto
this.client.telegramConsume({ sessionId })
) )
res.cookie('refreshToken', refreshToken, { res.cookie('refreshToken', refreshToken, {

View File

@ -1,45 +1,13 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices' import type { ClientGrpc } from '@nestjs/microservices'
import { import { InjectGrpc } from '@teacinema/common'
AuthServiceClient, import { AuthServiceClient } from '@teacinema/contracts/gen/auth'
RefreshRequest,
SendOtpRequest, import { AbstractGrpcClient } from '../../shared/grpc/abstract-grpc.client'
TelegramConsumeRequest,
TelegramVerifyRequest,
VerifyOtpRequest
} from '@teacinema/contracts/gen/auth'
@Injectable() @Injectable()
export class AuthClientGrpc implements OnModuleInit { export class AuthClientGrpc extends AbstractGrpcClient<AuthServiceClient> {
private authService: AuthServiceClient constructor(@InjectGrpc('AUTH_PACKAGE') client: ClientGrpc) {
super(client, 'AuthService')
constructor(@Inject('AUTH_PACKAGE') private readonly client: ClientGrpc) {}
onModuleInit() {
this.authService = this.client.getService<AuthServiceClient>('AuthService')
}
public sendOtp(request: SendOtpRequest) {
return this.authService.sendOtp(request)
}
public verifyOtp(request: VerifyOtpRequest) {
return this.authService.verifyOtp(request)
}
public refresh(request: RefreshRequest) {
return this.authService.refresh(request)
}
public telegramInit() {
return this.authService.telegramInit({})
}
public telegramVerify(request: TelegramVerifyRequest) {
return this.authService.telegramVerify(request)
}
public telegramConsume(request: TelegramConsumeRequest) {
return this.authService.telegramConsume(request)
} }
} }

View File

@ -1,31 +1,11 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { ConfigService } from '@nestjs/config' import { GrpcModule } from '@teacinema/common'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { PROTO_PATHS } from '@teacinema/contracts'
import { AccountModule } from '../account/account.module'
import { AuthController } from './auth.controller' import { AuthController } from './auth.controller'
import { AuthClientGrpc } from './auth.grpc' import { AuthClientGrpc } from './auth.grpc'
@Module({ @Module({
imports: [ imports: [GrpcModule.register(['AUTH_PACKAGE'])],
ClientsModule.registerAsync([
{
name: 'AUTH_PACKAGE',
useFactory: (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'auth.v1',
protoPath: PROTO_PATHS.AUTH,
url: configService.getOrThrow<string>('AUTH_GRPC_URL')
}
}),
inject: [ConfigService]
}
]),
AccountModule
],
controllers: [AuthController], controllers: [AuthController],
providers: [AuthClientGrpc] providers: [AuthClientGrpc]
}) })

View File

@ -7,7 +7,6 @@ import {
Patch Patch
} from '@nestjs/common' } from '@nestjs/common'
import { ApiBearerAuth, ApiOkResponse, ApiOperation } from '@nestjs/swagger' import { ApiBearerAuth, ApiOkResponse, ApiOperation } from '@nestjs/swagger'
import { lastValueFrom } from 'rxjs'
import { CurrentUser, Protected } from '../../shared/decorators' import { CurrentUser, Protected } from '../../shared/decorators'
@ -32,7 +31,7 @@ export class UsersController {
@Get('me') @Get('me')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
public async getMe(@CurrentUser() userId: string) { public async getMe(@CurrentUser() userId: string) {
const { user } = await lastValueFrom(this.client.getMe({ id: userId })) const { user } = await this.client.call('getMe', { id: userId })
return user return user
} }
@ -49,6 +48,6 @@ export class UsersController {
@CurrentUser() userId: string, @CurrentUser() userId: string,
@Body() dto: PatchUserRequestDto @Body() dto: PatchUserRequestDto
) { ) {
return this.client.patchUser({ userId, ...dto }) return this.client.call('patchUser', { userId, ...dto })
} }
} }

View File

@ -1,27 +1,13 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common' import { Injectable } from '@nestjs/common'
import type { ClientGrpc } from '@nestjs/microservices' import type { ClientGrpc } from '@nestjs/microservices'
import { import { InjectGrpc } from '@teacinema/common'
GetMeRequest, import { UsersServiceClient } from '@teacinema/contracts/gen/users'
PatchUserRequest,
UsersServiceClient import { AbstractGrpcClient } from '../../shared/grpc/abstract-grpc.client'
} from '@teacinema/contracts/gen/users'
@Injectable() @Injectable()
export class UsersClientGrpc implements OnModuleInit { export class UsersClientGrpc extends AbstractGrpcClient<UsersServiceClient> {
private usersService: UsersServiceClient constructor(@InjectGrpc('USERS_PACKAGE') client: ClientGrpc) {
super(client, 'UsersService')
constructor(@Inject('USERS_PACKAGE') private readonly client: ClientGrpc) {}
onModuleInit() {
this.usersService =
this.client.getService<UsersServiceClient>('UsersService')
}
public getMe(request: GetMeRequest) {
return this.usersService.getMe(request)
}
public patchUser(request: PatchUserRequest) {
return this.usersService.patchUser(request)
} }
} }

View File

@ -1,28 +1,11 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { ConfigService } from '@nestjs/config' import { GrpcModule } from '@teacinema/common'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { PROTO_PATHS } from '@teacinema/contracts'
import { UsersController } from './users.controller' import { UsersController } from './users.controller'
import { UsersClientGrpc } from './users.grpc' import { UsersClientGrpc } from './users.grpc'
@Module({ @Module({
imports: [ imports: [GrpcModule.register(['USERS_PACKAGE'])],
ClientsModule.registerAsync([
{
name: 'USERS_PACKAGE',
useFactory: (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'users.v1',
protoPath: PROTO_PATHS.USERS,
url: configService.getOrThrow<string>('USERS_GRPC_URL')
}
}),
inject: [ConfigService]
}
])
],
controllers: [UsersController], controllers: [UsersController],
providers: [UsersClientGrpc], providers: [UsersClientGrpc],
exports: [UsersClientGrpc] exports: [UsersClientGrpc]

View File

@ -0,0 +1,32 @@
import { OnModuleInit } from '@nestjs/common'
import { ClientGrpc } from '@nestjs/microservices'
import { lastValueFrom, Observable } from 'rxjs'
type UnwrapObservable<U> = U extends Observable<infer R> ? R : U
export abstract class AbstractGrpcClient<
T extends Record<keyof T, (request: any) => Observable<any>>
> implements OnModuleInit {
protected service!: T
protected constructor(
private readonly client: ClientGrpc,
private readonly serviceName: string
) {}
public onModuleInit(): any {
this.service = this.client.getService<T>(this.serviceName)
}
public async call<K extends keyof T>(
method: K,
payload: Parameters<T[K]>[0]
): Promise<UnwrapObservable<ReturnType<T[K]>>> {
const observable = this.service[method](payload) as Observable<
ReturnType<T[K]>
>
const result = await lastValueFrom(observable)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return result as UnwrapObservable<ReturnType<T[K]>>
}
}

View File

@ -8,7 +8,6 @@ import {
import { Reflector } from '@nestjs/core' import { Reflector } from '@nestjs/core'
import { Role } from '@teacinema/contracts/gen/account' import { Role } from '@teacinema/contracts/gen/account'
import { Request } from 'express' import { Request } from 'express'
import { lastValueFrom } from 'rxjs'
import { AccountClientGrpc } from '../../modules/account/account.grpc' import { AccountClientGrpc } from '../../modules/account/account.grpc'
import { ROLES_KEY } from '../decorators' import { ROLES_KEY } from '../decorators'
@ -37,9 +36,7 @@ export class RolesGuard implements CanActivate {
throw new ForbiddenException('User context missing') throw new ForbiddenException('User context missing')
} }
const account = await lastValueFrom( const account = await this.accountClient.call('getAccount', { id: user.id })
this.accountClient.getAccount({ id: user.id })
)
if (!account) { if (!account) {
throw new NotFoundException('Account not found') throw new NotFoundException('Account not found')

View File

@ -1022,6 +1022,17 @@
load-esm "1.0.3" load-esm "1.0.3"
tslib "2.8.1" tslib "2.8.1"
"@nestjs/common@^11.1.16":
version "11.1.16"
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.16.tgz#1f4061f66d7ae63e407f8d2be2aaeaf83097332b"
integrity sha512-JSIeW+USuMJkkcNbiOdcPkVCeI3TSnXstIVEPpp3HiaKnPRuSbUUKm9TY9o/XpIcPHWUOQItAtC5BiAwFdVITQ==
dependencies:
uid "2.0.2"
file-type "21.3.0"
iterare "1.2.1"
load-esm "1.0.3"
tslib "2.8.1"
"@nestjs/config@^4.0.2": "@nestjs/config@^4.0.2":
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.2.tgz#a2777a1fd2d0d594bab3953f50fbca95c14cce52" resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.2.tgz#a2777a1fd2d0d594bab3953f50fbca95c14cce52"
@ -1031,6 +1042,15 @@
dotenv-expand "12.0.1" dotenv-expand "12.0.1"
lodash "4.17.21" lodash "4.17.21"
"@nestjs/config@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.3.tgz#bb24a5f64e4e3ccf6fbf9e02f63303d14d7f308e"
integrity sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==
dependencies:
dotenv "17.2.3"
dotenv-expand "12.0.3"
lodash "4.17.23"
"@nestjs/core@^11.0.1": "@nestjs/core@^11.0.1":
version "11.1.12" version "11.1.12"
resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.1.12.tgz#cdbff023cc87ed071aebafeacb209bc5997da1e4" resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.1.12.tgz#cdbff023cc87ed071aebafeacb209bc5997da1e4"
@ -1068,6 +1088,14 @@
iterare "1.2.1" iterare "1.2.1"
tslib "2.8.1" tslib "2.8.1"
"@nestjs/microservices@^11.1.16":
version "11.1.16"
resolved "https://registry.yarnpkg.com/@nestjs/microservices/-/microservices-11.1.16.tgz#6f445c44adcee88af983830e0667c93ccefe2869"
integrity sha512-eG/ArIq0UJyR3i/GTYuApA4OZylhuLGacVaVT9mMxQgT7ZTpp5CZgOwLNdcUUdOS6qypK3waG1m2AC54xzdf0Q==
dependencies:
iterare "1.2.1"
tslib "2.8.1"
"@nestjs/platform-express@^11.0.1": "@nestjs/platform-express@^11.0.1":
version "11.1.12" version "11.1.12"
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-11.1.12.tgz#c9449230c3b8843370bca65d5b2c9909d3e3f0f6" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-11.1.12.tgz#c9449230c3b8843370bca65d5b2c9909d3e3f0f6"
@ -1216,11 +1244,16 @@
"@sinonjs/commons" "^3.0.1" "@sinonjs/commons" "^3.0.1"
"@teacinema/common@^1.0.0": "@teacinema/common@^1.0.0":
version "1.0.2" version "1.1.3"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcommon/-/1.0.2/common-1.0.2.tgz#72b98bc641042e8c6a42c2285bfd49d84ea6a195" resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcommon/-/1.1.3/common-1.1.3.tgz#73c8e3642f37d9c6574f6a1e5b542dd6cbfdbf2f"
integrity sha512-gMPQtWtlVQ5lEm4Z9eO/Iqd+5jyjsoHYR1HgD+JmkTai+Gj+Vz6UN4hNL2tso1mGIe6foNfYozbin2TEbBRzdg== integrity sha512-Bk2JzG3P1K7YFoFywtSP3zHxNfUpy+NyboqChj+4GZ6JuoWg2V1wkrjMljeE90cPOaqCiNHxk08zX037waiKJg==
dependencies:
"@nestjs/common" "^11.1.16"
"@nestjs/config" "^4.0.3"
"@nestjs/microservices" "^11.1.16"
"@teacinema/contracts" "^1.1.3"
"@teacinema/contracts@^1.0.0": "@teacinema/contracts@^1.0.0", "@teacinema/contracts@^1.1.3":
version "1.1.3" version "1.1.3"
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.1.3/contracts-1.1.3.tgz#e08600a8dc09977dae35654d372e29e411029e33" resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.1.3/contracts-1.1.3.tgz#e08600a8dc09977dae35654d372e29e411029e33"
integrity sha512-kwcSh2YIxmoBJQ56L+tQpGj12ySwNwf9KRNcUqhPffLPO60PR5aiKA2eN2SfKc23TKp0YoSkdC9LWeor0a9Ysg== integrity sha512-kwcSh2YIxmoBJQ56L+tQpGj12ySwNwf9KRNcUqhPffLPO60PR5aiKA2eN2SfKc23TKp0YoSkdC9LWeor0a9Ysg==
@ -2547,11 +2580,23 @@ dotenv-expand@12.0.1:
dependencies: dependencies:
dotenv "^16.4.5" dotenv "^16.4.5"
dotenv-expand@12.0.3:
version "12.0.3"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-12.0.3.tgz#6323ceca51ca0c1b1f0055e2aba39c79781739a6"
integrity sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==
dependencies:
dotenv "^16.4.5"
dotenv@16.4.7: dotenv@16.4.7:
version "16.4.7" version "16.4.7"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26"
integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==
dotenv@17.2.3:
version "17.2.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.2.3.tgz#ad995d6997f639b11065f419a22fabf567cdb9a2"
integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==
dotenv@^16.4.5: dotenv@^16.4.5:
version "16.6.1" version "16.6.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020"
@ -3918,6 +3963,11 @@ lodash@4.17.21, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lodash@4.17.23:
version "4.17.23"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
log-symbols@^4.1.0: log-symbols@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"