mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2025-12-16 05:09:26 +00:00
add auth docs, add eslint import/order
This commit is contained in:
30
.eslintrc.js
30
.eslintrc.js
@@ -9,6 +9,7 @@ module.exports = {
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:import/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
@@ -21,5 +22,32 @@ module.exports = {
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
'newlines-between': 'always',
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true,
|
||||
},
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
},
|
||||
],
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||
{ blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: ['const', 'let', 'var'],
|
||||
next: ['const', 'let', 'var'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: true,
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
"deleteOutDir": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "@nestjs/swagger",
|
||||
"options": {
|
||||
"classValidatorShim": true,
|
||||
"introspectComments": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,11 @@
|
||||
"@types/node": "18.16.12",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
||||
"@typescript-eslint/parser": "^5.59.6",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jest": "29.5.0",
|
||||
"prettier": "^2.8.8",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import * as process from 'process'
|
||||
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
|
||||
import { JwtStrategy } from './modules/auth/strategies/jwt.strategy'
|
||||
import { ConfigModule } from './settings/config.module'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
import { MailerModule } from '@nestjs-modules/mailer'
|
||||
|
||||
import { FileUploadService } from './infrastructure/file-upload-service/file-upload.service'
|
||||
import { AuthModule } from './modules/auth/auth.module'
|
||||
import { JwtRefreshStrategy } from './modules/auth/strategies/jwt-refresh.strategy'
|
||||
import { JwtStrategy } from './modules/auth/strategies/jwt.strategy'
|
||||
import { CardsModule } from './modules/cards/cards.module'
|
||||
import { DecksModule } from './modules/decks/decks.module'
|
||||
import { UsersModule } from './modules/users/users.module'
|
||||
import { PrismaModule } from './prisma.module'
|
||||
import { MailerModule } from '@nestjs-modules/mailer'
|
||||
import * as process from 'process'
|
||||
import { JwtRefreshStrategy } from './modules/auth/strategies/jwt-refresh.strategy'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
import { DecksModule } from './modules/decks/decks.module'
|
||||
import { CardsModule } from './modules/cards/cards.module'
|
||||
import { FileUploadService } from './infrastructure/file-upload-service/file-upload.service'
|
||||
import { ConfigModule } from './settings/config.module'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
||||
@@ -8,11 +8,13 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
||||
const response = ctx.getResponse<Response>()
|
||||
const request = ctx.getRequest<Request>()
|
||||
const status = exception.getStatus()
|
||||
|
||||
if (status === 400) {
|
||||
const errorsResponse = {
|
||||
errorMessages: [],
|
||||
}
|
||||
const responseBody: any = exception.getResponse()
|
||||
|
||||
if (typeof responseBody.message === 'object') {
|
||||
responseBody.message.forEach(e => errorsResponse.errorMessages.push(e))
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { omit } from 'remeda'
|
||||
export const setCountKey = <K extends string, L extends string>(key: K, newKey: L) => {
|
||||
return <T extends Record<string, any>>(obj: T) => {
|
||||
obj[newKey] = obj['_count'][key]
|
||||
|
||||
return omit(obj, ['_count']) as Omit<T, '_count'> & { [P in L]: number }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsNumber, IsOptional } from 'class-validator'
|
||||
import { Type } from 'class-transformer'
|
||||
import { IsNumber, IsOptional } from 'class-validator'
|
||||
|
||||
export class PaginationDto {
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isObject } from 'remeda'
|
||||
|
||||
import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE } from './pagination.constants'
|
||||
|
||||
export class Pagination {
|
||||
@@ -17,6 +18,7 @@ export class Pagination {
|
||||
!isNaN(Number(query.itemsPerPage))
|
||||
? +query.itemsPerPage
|
||||
: DEFAULT_PAGE_SIZE
|
||||
|
||||
return { currentPage, itemsPerPage, ...query }
|
||||
}
|
||||
|
||||
@@ -31,6 +33,7 @@ export class Pagination {
|
||||
}
|
||||
) {
|
||||
const totalPages = Math.ceil(count / itemsPerPage)
|
||||
|
||||
return {
|
||||
pagination: {
|
||||
totalPages,
|
||||
|
||||
@@ -2,5 +2,6 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
||||
|
||||
export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => {
|
||||
const request = ctx.switchToHttp().getRequest()
|
||||
|
||||
return data ? request.cookies?.[data] : request.cookies
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { PrismaService } from '../../prisma.service'
|
||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
|
||||
|
||||
@Injectable()
|
||||
export class FileUploadService {
|
||||
|
||||
16
src/main.ts
16
src/main.ts
@@ -1,13 +1,15 @@
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { AppModule } from './app.module'
|
||||
import { Logger } from '@nestjs/common'
|
||||
import { HttpExceptionFilter } from './exception.filter'
|
||||
import * as cookieParser from 'cookie-parser'
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
|
||||
import * as cookieParser from 'cookie-parser'
|
||||
|
||||
import { AppModule } from './app.module'
|
||||
import { HttpExceptionFilter } from './exception.filter'
|
||||
import { pipesSetup } from './settings/pipes-setup'
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule)
|
||||
|
||||
app.enableCors({
|
||||
origin: true,
|
||||
credentials: true,
|
||||
@@ -19,13 +21,19 @@ async function bootstrap() {
|
||||
.setTitle('Flashcards')
|
||||
.setDescription('The config API description')
|
||||
.setVersion('1.0')
|
||||
.addTag('Auth')
|
||||
.addTag('Decks')
|
||||
.addTag('Cards')
|
||||
.addTag('Admin')
|
||||
.build()
|
||||
const document = SwaggerModule.createDocument(app, config)
|
||||
|
||||
SwaggerModule.setup('docs', app, document)
|
||||
pipesSetup(app)
|
||||
app.useGlobalFilters(new HttpExceptionFilter())
|
||||
await app.listen(process.env.PORT || 3000)
|
||||
const logger = new Logger('NestApplication')
|
||||
|
||||
logger.log(`Application is running on: ${await app.getUrl()}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,21 @@ import {
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common'
|
||||
import { RegistrationDto } from './dto/registration.dto'
|
||||
import { LocalAuthGuard, JwtAuthGuard, JwtRefreshGuard } from './guards'
|
||||
import { Response as ExpressResponse } from 'express'
|
||||
import { Cookies } from '../../infrastructure/decorators'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { ApiBody, ApiTags } from '@nestjs/swagger'
|
||||
import { Response as ExpressResponse } from 'express'
|
||||
|
||||
import { Cookies } from '../../infrastructure/decorators'
|
||||
|
||||
import {
|
||||
EmailVerificationDto,
|
||||
LoginDto,
|
||||
RecoverPasswordDto,
|
||||
RegistrationDto,
|
||||
ResendVerificationEmailDto,
|
||||
} from './dto'
|
||||
import { LoginResponse, UserEntity } from './entities/auth.entity'
|
||||
import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard } from './guards'
|
||||
import {
|
||||
CreateUserCommand,
|
||||
GetCurrentUserDataCommand,
|
||||
@@ -28,22 +38,29 @@ import {
|
||||
VerifyEmailCommand,
|
||||
} from './use-cases'
|
||||
|
||||
@ApiTags('Auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private commandBus: CommandBus) {}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('me')
|
||||
async getUserData(@Request() req) {
|
||||
async getUserData(@Request() req): Promise<UserEntity> {
|
||||
const userId = req.user.id
|
||||
|
||||
return await this.commandBus.execute(new GetCurrentUserDataCommand(userId))
|
||||
}
|
||||
|
||||
@HttpCode(200)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiBody({ type: LoginDto })
|
||||
@UseGuards(LocalAuthGuard)
|
||||
@Post('login')
|
||||
async login(@Request() req, @Res({ passthrough: true }) res: ExpressResponse) {
|
||||
async login(
|
||||
@Request() req,
|
||||
@Res({ passthrough: true }) res: ExpressResponse
|
||||
): Promise<LoginResponse> {
|
||||
const userData = req.user.data
|
||||
|
||||
res.cookie('refreshToken', userData.refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
@@ -55,63 +72,77 @@ export class AuthController {
|
||||
sameSite: 'none',
|
||||
secure: true,
|
||||
})
|
||||
|
||||
return { accessToken: req.user.data.accessToken }
|
||||
}
|
||||
|
||||
@HttpCode(201)
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@Post('sign-up')
|
||||
async registration(@Body() registrationData: RegistrationDto) {
|
||||
async registration(@Body() registrationData: RegistrationDto): Promise<UserEntity> {
|
||||
return await this.commandBus.execute(new CreateUserCommand(registrationData))
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Post('verify-email')
|
||||
async confirmRegistration(@Body('code') confirmationCode) {
|
||||
return await this.commandBus.execute(new VerifyEmailCommand(confirmationCode))
|
||||
async confirmRegistration(@Body() body: EmailVerificationDto): Promise<void> {
|
||||
return await this.commandBus.execute(new VerifyEmailCommand(body.code))
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Post('resend-verification-email')
|
||||
async resendVerificationEmail(@Body('userId') userId: string) {
|
||||
return await this.commandBus.execute(new ResendVerificationEmailCommand(userId))
|
||||
async resendVerificationEmail(@Body() body: ResendVerificationEmailDto): Promise<void> {
|
||||
return await this.commandBus.execute(new ResendVerificationEmailCommand(body.userId))
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Post('logout')
|
||||
async logout(
|
||||
@Cookies('refreshToken') refreshToken: string,
|
||||
@Res({ passthrough: true }) res: ExpressResponse
|
||||
) {
|
||||
): Promise<void> {
|
||||
if (!refreshToken) throw new UnauthorizedException()
|
||||
await this.commandBus.execute(new LogoutCommand(refreshToken))
|
||||
res.clearCookie('refreshToken')
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtRefreshGuard)
|
||||
@Get('refresh-token')
|
||||
async refreshToken(@Request() req, @Response({ passthrough: true }) res: ExpressResponse) {
|
||||
@Post('refresh-token')
|
||||
async refreshToken(
|
||||
@Request() req,
|
||||
@Response({ passthrough: true }) res: ExpressResponse
|
||||
): Promise<LoginResponse> {
|
||||
if (!req.cookies?.refreshToken) throw new UnauthorizedException()
|
||||
const userId = req.user.id
|
||||
const newTokens = await this.commandBus.execute(new RefreshTokenCommand(userId))
|
||||
|
||||
res.cookie('refreshToken', newTokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
// secure: true,
|
||||
path: '/v1/auth/refresh-token',
|
||||
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
||||
})
|
||||
|
||||
return {
|
||||
accessToken: newTokens.accessToken,
|
||||
}
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Post('recover-password')
|
||||
async recoverPassword(@Body('email') email: string) {
|
||||
return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(email))
|
||||
async recoverPassword(@Body() body: RecoverPasswordDto): Promise<void> {
|
||||
return await this.commandBus.execute(new SendPasswordRecoveryEmailCommand(body.email))
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Post('reset-password/:token')
|
||||
async resetPassword(@Body('password') password: string, @Param('token') token: string) {
|
||||
async resetPassword(
|
||||
@Body('password') password: string,
|
||||
@Param('token') token: string
|
||||
): Promise<void> {
|
||||
return await this.commandBus.execute(new ResetPasswordCommand(token, password))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { AuthService } from './auth.service'
|
||||
import { AuthController } from './auth.controller'
|
||||
import { UsersModule } from '../users/users.module'
|
||||
import { LocalStrategy } from './strategies/local.strategy'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
|
||||
import { UsersModule } from '../users/users.module'
|
||||
|
||||
import { AuthController } from './auth.controller'
|
||||
import { AuthService } from './auth.service'
|
||||
import { AuthRepository } from './infrastructure/auth.repository'
|
||||
import { LocalStrategy } from './strategies/local.strategy'
|
||||
import {
|
||||
CreateUserHandler,
|
||||
GetCurrentUserDataHandler,
|
||||
@@ -14,7 +17,6 @@ import {
|
||||
SendPasswordRecoveryEmailHandler,
|
||||
VerifyEmailHandler,
|
||||
} from './use-cases'
|
||||
import { AuthRepository } from './infrastructure/auth.repository'
|
||||
|
||||
const commandHandlers = [
|
||||
CreateUserHandler,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import * as process from 'process'
|
||||
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import { addDays } from 'date-fns'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import { UsersRepository } from '../users/infrastructure/users.repository'
|
||||
import * as process from 'process'
|
||||
|
||||
import { PrismaService } from '../../prisma.service'
|
||||
import { UsersRepository } from '../users/infrastructure/users.repository'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -21,6 +23,7 @@ export class AuthService {
|
||||
const refreshToken = jwt.sign(payload, refreshSecretKey, {
|
||||
expiresIn: '30d',
|
||||
})
|
||||
|
||||
await this.prisma.refreshToken.create({
|
||||
data: {
|
||||
userId: userId,
|
||||
@@ -29,6 +32,7 @@ export class AuthService {
|
||||
isRevoked: false,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
@@ -37,6 +41,7 @@ export class AuthService {
|
||||
|
||||
async checkCredentials(email: string, password: string) {
|
||||
const user = await this.usersRepository.findUserByEmail(email)
|
||||
|
||||
if (!user /*|| !user.emailConfirmation.isConfirmed*/)
|
||||
return {
|
||||
resultCode: 1,
|
||||
@@ -46,6 +51,7 @@ export class AuthService {
|
||||
},
|
||||
}
|
||||
const isPasswordValid = await this.isPasswordCorrect(password, user.password)
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return {
|
||||
resultCode: 1,
|
||||
@@ -58,6 +64,7 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
const tokensPair = await this.createJwtTokensPair(user.id)
|
||||
|
||||
return {
|
||||
resultCode: 0,
|
||||
data: tokensPair,
|
||||
|
||||
6
src/modules/auth/dto/email-verification.dto.ts
Normal file
6
src/modules/auth/dto/email-verification.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsUUID } from 'class-validator'
|
||||
|
||||
export class EmailVerificationDto {
|
||||
@IsUUID('4')
|
||||
code: string
|
||||
}
|
||||
6
src/modules/auth/dto/index.ts
Normal file
6
src/modules/auth/dto/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './email-verification.dto'
|
||||
export * from './login.dto'
|
||||
export * from './recover-password.dto'
|
||||
export * from './registration.dto'
|
||||
export * from './resend-verification-email.dto'
|
||||
export * from './update-auth.dto'
|
||||
9
src/modules/auth/dto/login.dto.ts
Normal file
9
src/modules/auth/dto/login.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsEmail, Length } from 'class-validator'
|
||||
|
||||
export class LoginDto {
|
||||
@Length(3, 30)
|
||||
password: string
|
||||
|
||||
@IsEmail()
|
||||
email: string
|
||||
}
|
||||
6
src/modules/auth/dto/recover-password.dto.ts
Normal file
6
src/modules/auth/dto/recover-password.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsEmail } from 'class-validator'
|
||||
|
||||
export class RecoverPasswordDto {
|
||||
@IsEmail()
|
||||
email: string
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { IsEmail, Length, IsOptional } from 'class-validator'
|
||||
import { IsEmail, IsOptional, Length } from 'class-validator'
|
||||
|
||||
export class RegistrationDto {
|
||||
@Length(3, 30)
|
||||
@IsOptional()
|
||||
name: string
|
||||
name?: string
|
||||
|
||||
@Length(3, 30)
|
||||
password: string
|
||||
|
||||
@IsEmail()
|
||||
email: string
|
||||
}
|
||||
|
||||
6
src/modules/auth/dto/resend-verification-email.dto.ts
Normal file
6
src/modules/auth/dto/resend-verification-email.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsUUID } from 'class-validator'
|
||||
|
||||
export class ResendVerificationEmailDto {
|
||||
@IsUUID()
|
||||
userId: string
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
|
||||
import { RegistrationDto } from './registration.dto'
|
||||
|
||||
export class UpdateAuthDto extends PartialType(RegistrationDto) {}
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
export class Auth {}
|
||||
import { OmitType } from '@nestjs/swagger'
|
||||
|
||||
export class User {
|
||||
id: string
|
||||
email: string
|
||||
password: string
|
||||
isEmailVerified: boolean
|
||||
name: string
|
||||
avatar: string
|
||||
created: string
|
||||
updated: string
|
||||
}
|
||||
|
||||
export class LoginResponse {
|
||||
accessToken: string
|
||||
}
|
||||
|
||||
export class UserEntity extends OmitType(User, ['password']) {}
|
||||
|
||||
@@ -6,6 +6,7 @@ export class BaseAuthGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||
const request = context.switchToHttp().getRequest()
|
||||
const exceptedAuthInput = 'Basic YWRtaW46cXdlcnR5'
|
||||
|
||||
if (!request.headers || !request.headers.authorization) {
|
||||
throw new UnauthorizedException([{ message: 'No any auth headers' }])
|
||||
} else {
|
||||
@@ -17,6 +18,7 @@ export class BaseAuthGuard implements CanActivate {
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
const req = context.switchToHttp().getRequest()
|
||||
|
||||
const res: boolean = await (super.canActivate(context) as Promise<boolean>)
|
||||
|
||||
if (!res) return false
|
||||
|
||||
// check DTO
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common'
|
||||
import { PassportStrategy } from '@nestjs/passport'
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt'
|
||||
|
||||
import { AppSettings } from '../../../settings/app-settings'
|
||||
|
||||
type JwtPayload = {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Inject, Injectable } from '@nestjs/common'
|
||||
import { PassportStrategy } from '@nestjs/passport'
|
||||
import { Strategy } from 'passport-jwt'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
import { AppSettings } from '../../../settings/app-settings'
|
||||
import { Request } from 'express'
|
||||
import { Strategy } from 'passport-jwt'
|
||||
|
||||
import { AppSettings } from '../../../settings/app-settings'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
|
||||
const cookieExtractor = function (req: Request) {
|
||||
let token = null
|
||||
|
||||
if (req && req.cookies) {
|
||||
token = req.cookies['refreshToken']
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'
|
||||
import { PassportStrategy } from '@nestjs/passport'
|
||||
import { Request as RequestType } from 'express'
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt'
|
||||
import { AuthService } from '../auth.service'
|
||||
|
||||
import { AppSettings } from '../../../settings/app-settings'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
import { Request as RequestType } from 'express'
|
||||
import { AuthService } from '../auth.service'
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
@@ -25,9 +26,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
|
||||
async validate(payload: any) {
|
||||
const user = await this.userService.getUserById(payload.userId)
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -35,6 +38,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
if (req.cookies && 'accessToken' in req.cookies && req.cookies.accessToken.length > 0) {
|
||||
return req.cookies.accessToken
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
||||
import { PassportStrategy } from '@nestjs/passport'
|
||||
import { Strategy } from 'passport-local'
|
||||
|
||||
import { AuthService } from '../auth.service'
|
||||
|
||||
@Injectable()
|
||||
@@ -13,9 +14,11 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
|
||||
async validate(email: string, password: string): Promise<any> {
|
||||
const newCredentials = await this.authService.checkCredentials(email, password)
|
||||
|
||||
if (newCredentials.resultCode === 1) {
|
||||
throw new UnauthorizedException('Invalid credentials')
|
||||
}
|
||||
|
||||
return newCredentials
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { HttpStatus } from '@nestjs/common'
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import * as request from 'supertest'
|
||||
import { HttpStatus } from '@nestjs/common'
|
||||
|
||||
import { AppModule } from '../../../../app.module'
|
||||
import { RegistrationDto } from '../../dto/registration.dto'
|
||||
import { RegistrationDto } from '../../dto'
|
||||
|
||||
describe('AuthController (e2e)', () => {
|
||||
let app
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { CreateUserInput, UserViewType } from '../../../types/types'
|
||||
import { addHours } from 'date-fns'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { CreateUserInput, UserViewType } from '../../../types/types'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
import { RegistrationDto } from '../dto'
|
||||
|
||||
export class CreateUserCommand {
|
||||
constructor(public readonly user: { name: string; password: string; email: string }) {}
|
||||
constructor(public readonly user: RegistrationDto) {}
|
||||
}
|
||||
|
||||
@CommandHandler(CreateUserCommand)
|
||||
@@ -29,6 +31,7 @@ export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
|
||||
isEmailVerified: false,
|
||||
}
|
||||
const createdUser = await this.usersRepository.createUser(newUser)
|
||||
|
||||
if (!createdUser) {
|
||||
return null
|
||||
}
|
||||
@@ -37,6 +40,7 @@ export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
|
||||
name: createdUser.name,
|
||||
verificationToken: verificationToken,
|
||||
})
|
||||
|
||||
return {
|
||||
id: createdUser.id,
|
||||
name: createdUser.name,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { UserViewType } from '../../../types/types'
|
||||
import { UnauthorizedException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { pick } from 'remeda'
|
||||
|
||||
import { UserViewType } from '../../../types/types'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
|
||||
export class GetCurrentUserDataCommand {
|
||||
@@ -17,6 +18,6 @@ export class GetCurrentUserDataHandler implements ICommandHandler<GetCurrentUser
|
||||
|
||||
if (!user) throw new UnauthorizedException()
|
||||
|
||||
return pick(user, ['email', 'name', 'id', 'isEmailVerified', 'avatar'])
|
||||
return pick(user, ['email', 'name', 'id', 'isEmailVerified', 'avatar', 'created', 'updated'])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { Logger } from '@nestjs/common'
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
|
||||
export class LogoutCommand {
|
||||
constructor(public readonly refreshToken: string) {}
|
||||
}
|
||||
@@ -17,13 +18,18 @@ export class LogoutHandler implements ICommandHandler<LogoutCommand> {
|
||||
const token = command.refreshToken
|
||||
|
||||
const secretKey = process.env.JWT_SECRET_KEY
|
||||
|
||||
if (!secretKey) throw new Error('JWT_SECRET_KEY is not defined')
|
||||
|
||||
try {
|
||||
const decoded: any = jwt.verify(token, secretKey)
|
||||
return this.usersRepository.revokeToken(decoded.userId, token)
|
||||
|
||||
await this.usersRepository.revokeToken(decoded.userId, token)
|
||||
|
||||
return null
|
||||
} catch (e) {
|
||||
this.logger.log(`Decoding error: ${e}`)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { AuthRepository } from '../infrastructure/auth.repository'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import { addDays } from 'date-fns'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
|
||||
import { AuthRepository } from '../infrastructure/auth.repository'
|
||||
|
||||
export class RefreshTokenCommand {
|
||||
constructor(public readonly userId: string) {}
|
||||
@@ -26,7 +27,9 @@ export class RefreshTokenHandler implements ICommandHandler<RefreshTokenCommand>
|
||||
expiresIn: '30d',
|
||||
})
|
||||
const expiresIn = addDays(new Date(), 30)
|
||||
|
||||
await this.authRepository.createRefreshToken(userId, refreshToken, expiresIn)
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
|
||||
@@ -28,6 +29,7 @@ export class ResendVerificationEmailHandler
|
||||
}
|
||||
|
||||
const updatedUser = await this.usersRepository.updateVerificationToken(user.id)
|
||||
|
||||
await this.usersService.sendConfirmationEmail({
|
||||
email: updatedUser.user.email,
|
||||
name: updatedUser.user.name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
import { UsersService } from '../../users/services/users.service'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export class SendPasswordRecoveryEmailCommand {
|
||||
constructor(public readonly email: string) {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { isBefore } from 'date-fns'
|
||||
|
||||
import { UsersRepository } from '../../users/infrastructure/users.repository'
|
||||
|
||||
export class VerifyEmailCommand {
|
||||
@@ -15,6 +16,7 @@ export class VerifyEmailHandler implements ICommandHandler<VerifyEmailCommand> {
|
||||
const token = command.token
|
||||
|
||||
const verificationWithUser = await this.usersRepository.findUserByVerificationToken(token)
|
||||
|
||||
if (!verificationWithUser) throw new NotFoundException('User not found')
|
||||
|
||||
if (verificationWithUser.isEmailVerified)
|
||||
@@ -22,14 +24,17 @@ export class VerifyEmailHandler implements ICommandHandler<VerifyEmailCommand> {
|
||||
|
||||
const dbToken = verificationWithUser.verificationToken
|
||||
const isTokenExpired = isBefore(verificationWithUser.verificationTokenExpiry, new Date())
|
||||
|
||||
if (dbToken !== token || isTokenExpired) {
|
||||
return false
|
||||
}
|
||||
|
||||
const result = await this.usersRepository.updateEmailVerification(verificationWithUser.userId)
|
||||
|
||||
if (!result) {
|
||||
throw new NotFoundException('User not found')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,17 @@ import {
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { FileFieldsInterceptor } from '@nestjs/platform-express'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
|
||||
import { JwtAuthGuard } from '../auth/guards'
|
||||
|
||||
import { CardsService } from './cards.service'
|
||||
import { UpdateCardDto } from './dto'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { DeleteCardByIdCommand, GetDeckByIdCommand, UpdateCardCommand } from './use-cases'
|
||||
import { JwtAuthGuard } from '../auth/guards'
|
||||
import { FileFieldsInterceptor } from '@nestjs/platform-express'
|
||||
|
||||
@ApiTags('Cards')
|
||||
@Controller('cards')
|
||||
export class CardsController {
|
||||
constructor(private readonly decksService: CardsService, private commandBus: CommandBus) {}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { CardsService } from './cards.service'
|
||||
import { CardsController } from './cards.controller'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
import { DeleteCardByIdHandler, GetDeckByIdHandler, UpdateCardHandler } from './use-cases'
|
||||
import { CardsRepository } from './infrastructure/cards.repository'
|
||||
|
||||
import { FileUploadService } from '../../infrastructure/file-upload-service/file-upload.service'
|
||||
|
||||
import { CardsController } from './cards.controller'
|
||||
import { CardsService } from './cards.service'
|
||||
import { CardsRepository } from './infrastructure/cards.repository'
|
||||
import { DeleteCardByIdHandler, GetDeckByIdHandler, UpdateCardHandler } from './use-cases'
|
||||
|
||||
const commandHandlers = [GetDeckByIdHandler, DeleteCardByIdHandler, UpdateCardHandler]
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Length } from 'class-validator'
|
||||
|
||||
import { PaginationDto } from '../../../infrastructure/common/pagination/pagination.dto'
|
||||
import { IsOptionalOrEmptyString, IsOrderBy } from '../../../infrastructure/decorators'
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
|
||||
import { CreateCardDto } from './create-card.dto'
|
||||
|
||||
export class UpdateCardDto extends PartialType(CreateCardDto) {}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
import { GetAllCardsInDeckDto } from '../dto/get-all-cards.dto'
|
||||
import { CreateCardDto } from '../dto/create-card.dto'
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
|
||||
import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-order-by-object'
|
||||
import { UpdateCardDto } from '../dto/update-card.dto'
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
import { CreateCardDto, GetAllCardsInDeckDto, UpdateCardDto } from '../dto'
|
||||
|
||||
@Injectable()
|
||||
export class CardsRepository {
|
||||
@@ -31,6 +30,7 @@ export class CardsRepository {
|
||||
...card,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.deck.update({
|
||||
where: {
|
||||
id: deckId,
|
||||
@@ -41,6 +41,7 @@ export class CardsRepository {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return created
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -80,6 +81,7 @@ export class CardsRepository {
|
||||
take: itemsPerPage,
|
||||
}),
|
||||
])
|
||||
|
||||
return Pagination.transformPaginationData(result, { currentPage, itemsPerPage })
|
||||
} catch (e) {
|
||||
this.logger.error(e?.message)
|
||||
@@ -129,6 +131,7 @@ export class CardsRepository {
|
||||
id,
|
||||
},
|
||||
})
|
||||
|
||||
await tx.deck.update({
|
||||
where: {
|
||||
id: deleted.deckId,
|
||||
@@ -139,6 +142,7 @@ export class CardsRepository {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return deleted
|
||||
})
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { CardsRepository } from '../infrastructure/cards.repository'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { CardsRepository } from '../infrastructure/cards.repository'
|
||||
|
||||
export class DeleteCardByIdCommand {
|
||||
constructor(public readonly id: string, public readonly userId: string) {}
|
||||
@@ -12,10 +13,12 @@ export class DeleteCardByIdHandler implements ICommandHandler<DeleteCardByIdComm
|
||||
|
||||
async execute(command: DeleteCardByIdCommand) {
|
||||
const card = await this.cardsRepository.findCardById(command.id)
|
||||
|
||||
if (!card) throw new NotFoundException(`Card with id ${command.id} not found`)
|
||||
if (card.userId !== command.userId) {
|
||||
throw new BadRequestException(`You can't delete a card that you don't own`)
|
||||
}
|
||||
|
||||
return await this.cardsRepository.deleteCardById(command.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { CardsRepository } from '../infrastructure/cards.repository'
|
||||
|
||||
export class GetDeckByIdCommand {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { CardsRepository } from '../infrastructure/cards.repository'
|
||||
import { UpdateCardDto } from '../dto/update-card.dto'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
import { UpdateCardDto } from '../dto'
|
||||
import { CardsRepository } from '../infrastructure/cards.repository'
|
||||
|
||||
export class UpdateCardCommand {
|
||||
constructor(
|
||||
@@ -43,6 +44,7 @@ export class UpdateCardHandler implements ICommandHandler<UpdateCardCommand> {
|
||||
)
|
||||
|
||||
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
||||
|
||||
questionImg = result[0].fileUrl
|
||||
answerImg = result[1].fileUrl
|
||||
} else if (command.answerImg) {
|
||||
@@ -51,6 +53,7 @@ export class UpdateCardHandler implements ICommandHandler<UpdateCardCommand> {
|
||||
command.answerImg?.originalname
|
||||
)
|
||||
const result = await addAnswerImagePromise
|
||||
|
||||
answerImg = result.fileUrl
|
||||
} else if (command.questionImg) {
|
||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
||||
@@ -58,6 +61,7 @@ export class UpdateCardHandler implements ICommandHandler<UpdateCardCommand> {
|
||||
command.questionImg?.originalname
|
||||
)
|
||||
const result = await addQuestionImagePromise
|
||||
|
||||
questionImg = result.fileUrl
|
||||
}
|
||||
if (command.card.questionImg === '') {
|
||||
@@ -66,6 +70,7 @@ export class UpdateCardHandler implements ICommandHandler<UpdateCardCommand> {
|
||||
if (command.card.answerImg === '') {
|
||||
answerImg = null
|
||||
}
|
||||
|
||||
return await this.cardsRepository.updateCardById(command.cardId, {
|
||||
...command.card,
|
||||
answerImg,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { DomainResultNotification, ResultNotification } from './notification'
|
||||
import { validateOrReject } from 'class-validator'
|
||||
import { IEvent } from '@nestjs/cqrs'
|
||||
import { validateOrReject } from 'class-validator'
|
||||
|
||||
import { validationErrorsMapper, ValidationPipeErrorType } from '../../../settings/pipes-setup'
|
||||
|
||||
import { DomainResultNotification, ResultNotification } from './notification'
|
||||
|
||||
export class DomainError extends Error {
|
||||
constructor(message: string, public resultNotification: ResultNotification) {
|
||||
super(message)
|
||||
@@ -31,11 +33,14 @@ export const validateEntity = async <T extends object>(
|
||||
const resultNotification: DomainResultNotification<T> = mapErrorsToNotification<T>(
|
||||
validationErrorsMapper.mapValidationErrorArrayToValidationPipeErrorTypeArray(errors)
|
||||
)
|
||||
|
||||
resultNotification.addData(entity)
|
||||
resultNotification.addEvents(...events)
|
||||
|
||||
return resultNotification
|
||||
}
|
||||
const domainResultNotification = new DomainResultNotification<T>(entity)
|
||||
|
||||
domainResultNotification.addEvents(...events)
|
||||
|
||||
return domainResultNotification
|
||||
@@ -43,8 +48,10 @@ export const validateEntity = async <T extends object>(
|
||||
|
||||
export function mapErrorsToNotification<T>(errors: ValidationPipeErrorType[]) {
|
||||
const resultNotification = new DomainResultNotification<T>()
|
||||
|
||||
errors.forEach((item: ValidationPipeErrorType) =>
|
||||
resultNotification.addError(item.message, item.field, 1)
|
||||
)
|
||||
|
||||
return resultNotification
|
||||
}
|
||||
|
||||
@@ -13,9 +13,16 @@ import {
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { FileFieldsInterceptor } from '@nestjs/platform-express'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
|
||||
import { Pagination } from '../../infrastructure/common/pagination/pagination.service'
|
||||
import { JwtAuthGuard } from '../auth/guards'
|
||||
import { CreateCardDto, GetAllCardsInDeckDto } from '../cards/dto'
|
||||
|
||||
import { DecksService } from './decks.service'
|
||||
import { UpdateDeckDto, CreateDeckDto, GetAllDecksDto } from './dto'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import {
|
||||
CreateDeckCommand,
|
||||
DeleteDeckByIdCommand,
|
||||
@@ -27,11 +34,8 @@ import {
|
||||
SaveGradeCommand,
|
||||
CreateCardCommand,
|
||||
} from './use-cases'
|
||||
import { JwtAuthGuard } from '../auth/guards'
|
||||
import { CreateCardDto, GetAllCardsInDeckDto } from '../cards/dto'
|
||||
import { Pagination } from '../../infrastructure/common/pagination/pagination.service'
|
||||
import { FileFieldsInterceptor } from '@nestjs/platform-express'
|
||||
|
||||
@ApiTags('Decks')
|
||||
@Controller('decks')
|
||||
export class DecksController {
|
||||
constructor(private readonly decksService: DecksService, private commandBus: CommandBus) {}
|
||||
@@ -48,6 +52,7 @@ export class DecksController {
|
||||
@Body() createDeckDto: CreateDeckDto
|
||||
) {
|
||||
const userId = req.user.id
|
||||
|
||||
return this.commandBus.execute(
|
||||
new CreateDeckCommand({ ...createDeckDto, userId: userId }, files?.cover?.[0])
|
||||
)
|
||||
@@ -57,6 +62,7 @@ export class DecksController {
|
||||
@Get()
|
||||
findAll(@Query() query: GetAllDecksDto, @Req() req) {
|
||||
const finalQuery = Pagination.getPaginationData(query)
|
||||
|
||||
return this.commandBus.execute(new GetAllDecksCommand({ ...finalQuery, userId: req.user.id }))
|
||||
}
|
||||
|
||||
@@ -70,6 +76,7 @@ export class DecksController {
|
||||
@Get(':id/cards')
|
||||
findCardsInDeck(@Param('id') id: string, @Req() req, @Query() query: GetAllCardsInDeckDto) {
|
||||
const finalQuery = Pagination.getPaginationData(query)
|
||||
|
||||
return this.commandBus.execute(new GetAllCardsInDeckCommand(req.user.id, id, finalQuery))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { DecksService } from './decks.service'
|
||||
import { DecksController } from './decks.controller'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
|
||||
import { FileUploadService } from '../../infrastructure/file-upload-service/file-upload.service'
|
||||
import { CardsRepository } from '../cards/infrastructure/cards.repository'
|
||||
|
||||
import { DecksController } from './decks.controller'
|
||||
import { DecksService } from './decks.service'
|
||||
import { DecksRepository } from './infrastructure/decks.repository'
|
||||
import { GradesRepository } from './infrastructure/grades.repository'
|
||||
import {
|
||||
CreateDeckHandler,
|
||||
DeleteDeckByIdHandler,
|
||||
@@ -13,10 +19,6 @@ import {
|
||||
SaveGradeHandler,
|
||||
GetRandomCardInDeckHandler,
|
||||
} from './use-cases'
|
||||
import { DecksRepository } from './infrastructure/decks.repository'
|
||||
import { CardsRepository } from '../cards/infrastructure/cards.repository'
|
||||
import { GradesRepository } from './infrastructure/grades.repository'
|
||||
import { FileUploadService } from '../../infrastructure/file-upload-service/file-upload.service'
|
||||
|
||||
const commandHandlers = [
|
||||
CreateDeckHandler,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsBoolean, IsOptional, Length } from 'class-validator'
|
||||
import { Transform } from 'class-transformer'
|
||||
import { IsBoolean, IsOptional, Length } from 'class-validator'
|
||||
|
||||
export class CreateDeckDto {
|
||||
@Length(3, 30)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IsUUID } from 'class-validator'
|
||||
import { IsOptionalOrEmptyString, IsOrderBy } from '../../../infrastructure/decorators'
|
||||
|
||||
import { PaginationDto } from '../../../infrastructure/common/pagination/pagination.dto'
|
||||
import { IsOptionalOrEmptyString, IsOrderBy } from '../../../infrastructure/decorators'
|
||||
|
||||
export class GetAllDecksDto extends PaginationDto {
|
||||
@IsOptionalOrEmptyString()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
import { CreateDeckDto } from './create-deck.dto'
|
||||
import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators'
|
||||
import { IsBoolean } from 'class-validator'
|
||||
|
||||
import { IsOptionalOrEmptyString } from '../../../infrastructure/decorators'
|
||||
|
||||
import { CreateDeckDto } from './create-deck.dto'
|
||||
|
||||
export class UpdateDeckDto extends PartialType(CreateDeckDto) {
|
||||
@IsOptionalOrEmptyString()
|
||||
name: string
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
|
||||
|
||||
import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-order-by-object'
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
import { GetAllDecksDto } from '../dto'
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
import { createPrismaOrderBy } from '../../../infrastructure/common/helpers/get-order-by-object'
|
||||
|
||||
@Injectable()
|
||||
export class DecksRepository {
|
||||
@@ -103,6 +104,7 @@ export class DecksRepository {
|
||||
this.prisma
|
||||
.$queryRaw`SELECT MAX(card_count) as maxCardsCount FROM (SELECT COUNT(*) as card_count FROM card GROUP BY deckId) AS card_counts;`,
|
||||
])
|
||||
|
||||
return {
|
||||
maxCardsCount: Number(max[0].maxCardsCount),
|
||||
...Pagination.transformPaginationData([count, items], { currentPage, itemsPerPage }),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
|
||||
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
import { CreateCardDto } from '../../cards/dto'
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
|
||||
export class CreateCardCommand {
|
||||
constructor(
|
||||
@@ -34,6 +35,7 @@ export class CreateCardHandler implements ICommandHandler<CreateCardCommand> {
|
||||
)
|
||||
|
||||
const result = await Promise.all([addQuestionImagePromise, addAnswerImagePromise])
|
||||
|
||||
questionImg = result[0].fileUrl
|
||||
answerImg = result[1].fileUrl
|
||||
} else if (command.answerImg) {
|
||||
@@ -42,6 +44,7 @@ export class CreateCardHandler implements ICommandHandler<CreateCardCommand> {
|
||||
command.answerImg?.originalname
|
||||
)
|
||||
const result = await addAnswerImagePromise
|
||||
|
||||
answerImg = result.fileUrl
|
||||
} else if (command.questionImg) {
|
||||
const addQuestionImagePromise = this.fileUploadService.uploadFile(
|
||||
@@ -49,6 +52,7 @@ export class CreateCardHandler implements ICommandHandler<CreateCardCommand> {
|
||||
command.questionImg?.originalname
|
||||
)
|
||||
const result = await addQuestionImagePromise
|
||||
|
||||
questionImg = result.fileUrl
|
||||
}
|
||||
if (command.card.questionImg === '') {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
import { CreateDeckDto } from '../dto'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
|
||||
export class CreateDeckCommand {
|
||||
constructor(public readonly deck: CreateDeckDto, public readonly cover: Express.Multer.File) {}
|
||||
@@ -22,6 +23,7 @@ export class CreateDeckHandler implements ICommandHandler<CreateDeckCommand> {
|
||||
command.cover.buffer,
|
||||
command.cover.originalname
|
||||
)
|
||||
|
||||
cover = result.fileUrl
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class DeleteDeckByIdCommand {
|
||||
constructor(public readonly id: string, public readonly userId: string) {}
|
||||
@@ -12,10 +13,12 @@ export class DeleteDeckByIdHandler implements ICommandHandler<DeleteDeckByIdComm
|
||||
|
||||
async execute(command: DeleteDeckByIdCommand) {
|
||||
const deck = await this.deckRepository.findDeckById(command.id)
|
||||
|
||||
if (!deck) throw new NotFoundException(`Deck with id ${command.id} not found`)
|
||||
if (deck.userId !== command.userId) {
|
||||
throw new BadRequestException(`You can't delete a deck that you don't own`)
|
||||
}
|
||||
|
||||
return await this.deckRepository.deleteDeckById(command.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { GetAllCardsInDeckDto } from '../../cards/dto'
|
||||
import { ForbiddenException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { GetAllCardsInDeckDto } from '../../cards/dto'
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class GetAllCardsInDeckCommand {
|
||||
@@ -21,6 +22,7 @@ export class GetAllCardsInDeckHandler implements ICommandHandler<GetAllCardsInDe
|
||||
|
||||
async execute(command: GetAllCardsInDeckCommand) {
|
||||
const deck = await this.decksRepository.findDeckById(command.deckId)
|
||||
|
||||
if (!deck) throw new NotFoundException(`Deck with id ${command.deckId} not found`)
|
||||
|
||||
if (deck.userId !== command.userId && deck.isPrivate) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
import { GetAllDecksDto } from '../dto'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class GetAllDecksCommand {
|
||||
constructor(public readonly params: GetAllDecksDto) {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class GetDeckByIdCommand {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { ForbiddenException, NotFoundException } from '@nestjs/common'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { pick } from 'remeda'
|
||||
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class GetRandomCardInDeckCommand {
|
||||
constructor(public readonly userId: string, public readonly deckId: string) {}
|
||||
}
|
||||
@@ -20,6 +21,7 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
|
||||
|
||||
private async getSmartRandomCard(cards: Array<CardWithGrade>) {
|
||||
const selectionPool: Array<CardWithGrade> = []
|
||||
|
||||
cards.forEach(card => {
|
||||
// Calculate the average grade for the card
|
||||
const averageGrade =
|
||||
@@ -40,6 +42,7 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
|
||||
|
||||
async execute(command: GetRandomCardInDeckCommand) {
|
||||
const deck = await this.decksRepository.findDeckById(command.deckId)
|
||||
|
||||
if (!deck) throw new NotFoundException(`Deck with id ${command.deckId} not found`)
|
||||
|
||||
if (deck.userId !== command.userId && deck.isPrivate) {
|
||||
@@ -51,6 +54,7 @@ export class GetRandomCardInDeckHandler implements ICommandHandler<GetRandomCard
|
||||
command.deckId
|
||||
)
|
||||
const smartRandomCard = await this.getSmartRandomCard(cards)
|
||||
|
||||
return pick(smartRandomCard, ['id', 'question', 'answer', 'deckId'])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { ForbiddenException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { CardsRepository } from '../../cards/infrastructure/cards.repository'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
import { GradesRepository } from '../infrastructure/grades.repository'
|
||||
|
||||
@@ -24,6 +25,7 @@ export class SaveGradeHandler implements ICommandHandler<SaveGradeCommand> {
|
||||
|
||||
async execute(command: SaveGradeCommand) {
|
||||
const deck = await this.decksRepository.findDeckByCardId(command.args.cardId)
|
||||
|
||||
if (!deck)
|
||||
throw new NotFoundException(`Deck containing card with id ${command.args.cardId} not found`)
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
import { UpdateDeckDto } from '../dto'
|
||||
import { BadRequestException, NotFoundException } from '@nestjs/common'
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'
|
||||
|
||||
import { FileUploadService } from '../../../infrastructure/file-upload-service/file-upload.service'
|
||||
import { UpdateDeckDto } from '../dto'
|
||||
import { DecksRepository } from '../infrastructure/decks.repository'
|
||||
|
||||
export class UpdateDeckCommand {
|
||||
constructor(
|
||||
@@ -22,6 +23,7 @@ export class UpdateDeckHandler implements ICommandHandler<UpdateDeckCommand> {
|
||||
|
||||
async execute(command: UpdateDeckCommand) {
|
||||
const deck = await this.deckRepository.findDeckById(command.deckId)
|
||||
|
||||
if (!deck) {
|
||||
throw new NotFoundException(`Deck with id ${command.deckId} not found`)
|
||||
}
|
||||
@@ -36,10 +38,12 @@ export class UpdateDeckHandler implements ICommandHandler<UpdateDeckCommand> {
|
||||
command.cover.buffer,
|
||||
command.cover.originalname
|
||||
)
|
||||
|
||||
cover = result.fileUrl
|
||||
} else if (command.deck.cover === '') {
|
||||
cover = null
|
||||
}
|
||||
|
||||
return await this.deckRepository.updateDeckById(command.deckId, { ...command.deck, cover })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,16 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common'
|
||||
import { UsersService } from '../services/users.service'
|
||||
import { CreateUserDto } from '../dto/create-user.dto'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
import { BaseAuthGuard } from '../../auth/guards'
|
||||
import { CommandBus } from '@nestjs/cqrs'
|
||||
import { CreateUserCommand } from '../../auth/use-cases'
|
||||
import { CreateUserDto } from '../dto/create-user.dto'
|
||||
import { UsersService } from '../services/users.service'
|
||||
|
||||
@ApiTags('Admin')
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
constructor(private usersService: UsersService, private commandBus: CommandBus) {}
|
||||
@@ -25,7 +28,9 @@ export class UsersController {
|
||||
const { page, pageSize } = Pagination.getPaginationData(query)
|
||||
|
||||
const users = await this.usersService.getUsers(page, pageSize, query.name, query.email)
|
||||
|
||||
if (!users) throw new NotFoundException('Users not found')
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PartialType } from '@nestjs/mapped-types'
|
||||
|
||||
import { CreateUserDto } from './create-user.dto'
|
||||
|
||||
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { addHours } from 'date-fns'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
import {
|
||||
CreateUserInput,
|
||||
EntityWithPaginationType,
|
||||
@@ -5,17 +17,6 @@ import {
|
||||
UserViewType,
|
||||
VerificationWithUser,
|
||||
} from '../../../types/types'
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common'
|
||||
import { addHours } from 'date-fns'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PrismaService } from '../../../prisma.service'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import { Pagination } from '../../../infrastructure/common/pagination/pagination.service'
|
||||
|
||||
@Injectable()
|
||||
export class UsersRepository {
|
||||
@@ -52,6 +53,7 @@ export class UsersRepository {
|
||||
take: itemsPerPage,
|
||||
}),
|
||||
])
|
||||
|
||||
return Pagination.transformPaginationData(res, { currentPage, itemsPerPage })
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -100,6 +102,7 @@ export class UsersRepository {
|
||||
id,
|
||||
},
|
||||
})
|
||||
|
||||
return result.isDeleted
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -115,6 +118,7 @@ export class UsersRepository {
|
||||
async deleteAllUsers(): Promise<number> {
|
||||
try {
|
||||
const result = await this.prisma.user.deleteMany()
|
||||
|
||||
return result.count
|
||||
} catch (e) {
|
||||
this.logger.error(e?.message || e)
|
||||
@@ -128,6 +132,7 @@ export class UsersRepository {
|
||||
where: { id },
|
||||
include,
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
@@ -154,6 +159,7 @@ export class UsersRepository {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return user
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -176,9 +182,11 @@ export class UsersRepository {
|
||||
user: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!verification) {
|
||||
return null
|
||||
}
|
||||
|
||||
return verification
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -206,6 +214,7 @@ export class UsersRepository {
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return result.isEmailVerified
|
||||
} catch (e) {
|
||||
this.logger.error(e?.message || e)
|
||||
@@ -276,9 +285,11 @@ export class UsersRepository {
|
||||
user: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!resetPassword) {
|
||||
return null
|
||||
}
|
||||
|
||||
return resetPassword.user
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
@@ -335,9 +346,11 @@ export class UsersRepository {
|
||||
user: true,
|
||||
},
|
||||
})
|
||||
|
||||
if (!revokedToken.user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return revokedToken.user
|
||||
} catch (e) {
|
||||
this.logger.error(e?.message || e)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { UsersRepository } from '../infrastructure/users.repository'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import { MailerService } from '@nestjs-modules/mailer'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
import { UsersRepository } from '../infrastructure/users.repository'
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
@@ -23,6 +24,7 @@ export class UsersService {
|
||||
|
||||
async deleteAllUsers(): Promise<{ deleted: number }> {
|
||||
const deleted = await this.usersRepository.deleteAllUsers()
|
||||
|
||||
return { deleted }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { UsersService } from './services/users.service'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
|
||||
import { UsersController } from './api/users.controller'
|
||||
import { UsersRepository } from './infrastructure/users.repository'
|
||||
import { CqrsModule } from '@nestjs/cqrs'
|
||||
import { UsersService } from './services/users.service'
|
||||
|
||||
@Module({
|
||||
imports: [CqrsModule],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Global, Module } from '@nestjs/common'
|
||||
|
||||
import { PrismaService } from './prisma.service'
|
||||
|
||||
@Global()
|
||||
|
||||
@@ -42,4 +42,5 @@ export class AppSettings {
|
||||
}
|
||||
const env = new EnvironmentSettings((process.env.NODE_ENV || 'DEVELOPMENT') as EnvironmentsTypes)
|
||||
const auth = new AuthSettings(process.env)
|
||||
|
||||
export const appSettings = new AppSettings(env, auth)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Global, Module } from '@nestjs/common'
|
||||
|
||||
import { appSettings, AppSettings } from './app-settings'
|
||||
|
||||
//главный config модуль для управления env переменными импортируется в app.module.ts глобально
|
||||
|
||||
@@ -7,6 +7,7 @@ export const validationErrorsMapper = {
|
||||
): ValidationPipeErrorType[] {
|
||||
return errors.flatMap(error => {
|
||||
const constraints = error.constraints ?? []
|
||||
|
||||
return Object.entries(constraints).map(([_, value]) => ({
|
||||
field: error.property,
|
||||
message: value,
|
||||
@@ -26,6 +27,7 @@ export function pipesSetup(app: INestApplication) {
|
||||
exceptionFactory: (errors: ValidationError[]) => {
|
||||
const err =
|
||||
validationErrorsMapper.mapValidationErrorArrayToValidationPipeErrorTypeArray(errors)
|
||||
|
||||
throw new BadRequestException(err)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import { INestApplication } from '@nestjs/common'
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
import * as request from 'supertest'
|
||||
|
||||
import { AppModule } from '../src/app.module'
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
|
||||
Reference in New Issue
Block a user