feat: add auth module

This commit is contained in:
Sergey Krylov 2026-01-18 12:48:20 +03:00
parent d3b4cd0f00
commit 67f60f1137
15 changed files with 150 additions and 12 deletions

View File

@ -25,6 +25,8 @@
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},

View File

@ -1,11 +1,13 @@
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { AuthModule } from '../modules/auth/auth.module'
import { AppController } from './app.controller'
import { AppService } from './app.service'
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true })],
imports: [ConfigModule.forRoot({ isGlobal: true }), AuthModule],
controllers: [AppController],
providers: [AppService]
})

View File

@ -0,0 +1,9 @@
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'
import { ConfigService } from '@nestjs/config'
export function getCorsConfig(configService: ConfigService): CorsOptions {
return {
origin: configService.getOrThrow<string>('HTTP_CORS').split(','),
credentials: true
}
}

3
src/core/config/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './cors.config'
export * from './swagger.config'
export * from './validation-pipe.config'

View File

@ -0,0 +1,8 @@
import { DocumentBuilder } from '@nestjs/swagger'
export const swaggerConfig = new DocumentBuilder()
.setTitle('Teacinema API')
.setDescription('API Gateway for Teacinema microservices')
.setVersion('1.0.0')
.addBearerAuth()
.build()

View File

@ -0,0 +1,6 @@
import { ValidationPipe } from '@nestjs/common'
export const validationPipe = new ValidationPipe({
transform: true,
whitelist: true
})

View File

@ -1,9 +1,10 @@
import { Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { SwaggerModule } from '@nestjs/swagger'
import { AppModule } from './core/app.module'
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
@ -11,17 +12,9 @@ async function bootstrap() {
const logger = new Logger()
const config = app.get(ConfigService)
app.enableCors({
origin: config.getOrThrow<string>('HTTP_CORS').split(','),
credentials: true
})
app.useGlobalPipes(validationPipe)
const swaggerConfig = new DocumentBuilder()
.setTitle('Teacinema API')
.setDescription('API Gateway for Teacinema microservices')
.setVersion('1.0.0')
.addBearerAuth()
.build()
app.enableCors(getCorsConfig(config))
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig)
SwaggerModule.setup('docs', app, swaggerDocument, {

View File

@ -0,0 +1,19 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'
import { ApiOperation } from '@nestjs/swagger'
import { SendOtpRequestDto } from './dto'
@Controller('auth')
export class AuthController {
@ApiOperation({
summary: 'Send OTP',
description: 'Send verification code to the user phone or email'
})
@Post('otp/send')
@HttpCode(HttpStatus.OK)
public sendOtp(@Body() dto: SendOtpRequestDto) {
console.log('data', dto)
return { ok: true }
}
}

View File

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
@Module({
controllers: [AuthController]
})
export class AuthModule {}

View File

@ -0,0 +1 @@
export * from './rq'

View File

@ -0,0 +1 @@
export * from './send-otp.request.dto'

View File

@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsEnum, IsString, Validate } from 'class-validator'
import { IdentifierValidator } from '../../../../shared/validators'
export class SendOtpRequestDto {
@ApiProperty({
description: 'User phone or email',
example: '+791234567890'
})
@Validate(IdentifierValidator)
@IsString()
public identifier: string
@ApiProperty({
description: 'Type of identifier',
example: 'phone',
enum: ['phone', 'email']
})
@IsEnum(['phone', 'email'])
public type: 'phone' | 'email'
}

View File

@ -0,0 +1,35 @@
import {
isEmail,
isPhoneNumber,
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface
} from 'class-validator'
import { SendOtpRequestDto } from '../../modules/auth/dto'
@ValidatorConstraint({ name: 'IdentifierValidator', async: false })
export class IdentifierValidator implements ValidatorConstraintInterface {
public validate(value: string, args: ValidationArguments) {
const object = args.object as SendOtpRequestDto
if (object.type === 'email') {
return typeof value === 'string' && isEmail(value)
} else if (object.type === 'phone') {
return typeof value === 'string' && isPhoneNumber(value)
}
return false
}
public defaultMessage(args: ValidationArguments) {
const object = args.object as SendOtpRequestDto
if (object.type === 'email') {
return 'identifier must be a valid email'
} else if (object.type === 'phone') {
return 'identifier must be a valid phone number'
}
return 'Invalid identifier'
}
}

View File

@ -0,0 +1 @@
export * from './identifier.validator'

View File

@ -1359,6 +1359,11 @@
"@types/methods" "^1.1.4"
"@types/superagent" "^8.1.0"
"@types/validator@^13.15.3":
version "13.15.10"
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.10.tgz#742b77ec34d58554b94a76a14cef30d59e3c16b9"
integrity sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@ -2112,6 +2117,20 @@ cjs-module-lexer@^2.1.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca"
integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==
class-transformer@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
class-validator@^0.14.3:
version "0.14.3"
resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.3.tgz#834a4caafa8359aed73d7708badb4cf271be50fe"
integrity sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==
dependencies:
"@types/validator" "^13.15.3"
libphonenumber-js "^1.11.1"
validator "^13.15.20"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@ -3666,6 +3685,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
libphonenumber-js@^1.11.1:
version "1.12.34"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.34.tgz#20ed9da39e930e98eff4b6e24a8d8480303658af"
integrity sha512-v/Ip8k8eYdp7bINpzqDh46V/PaQ8sK+qi97nMQgjZzFlb166YFqlR/HVI+MzsI9JqcyyVWCOipmmretiaSyQyw==
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@ -4953,6 +4977,11 @@ v8-to-istanbul@^9.0.1:
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^2.0.0"
validator@^13.15.20:
version "13.15.26"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.26.tgz#36c3deeab30e97806a658728a155c66fcaa5b944"
integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==
vary@^1, vary@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"