diff --git a/src/app.module.ts b/src/app.module.ts index 9b90ac3..d57cb03 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,9 +7,11 @@ 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' @Module({ imports: [ + CqrsModule, ConfigModule, UsersModule, AuthModule, @@ -29,6 +31,6 @@ import { JwtRefreshStrategy } from './modules/auth/strategies/jwt-refresh.strate ], controllers: [], providers: [JwtStrategy, JwtRefreshStrategy], - exports: [], + exports: [CqrsModule], }) export class AppModule {} diff --git a/src/exception.filter.ts b/src/exception.filter.ts index 630c25d..b7889b1 100644 --- a/src/exception.filter.ts +++ b/src/exception.filter.ts @@ -10,18 +10,19 @@ export class HttpExceptionFilter implements ExceptionFilter { const status = exception.getStatus() if (status === 400) { const errorsResponse = { - errorsMessages: [], + errorMessages: [], } const responseBody: any = exception.getResponse() if (typeof responseBody.message === 'object') { - responseBody.message.forEach(e => errorsResponse.errorsMessages.push(e)) + responseBody.message.forEach(e => errorsResponse.errorMessages.push(e)) } else { - errorsResponse.errorsMessages.push(responseBody.message) + errorsResponse.errorMessages.push(responseBody.message) } response.status(status).json(errorsResponse) } else { response.status(status).json({ statusCode: status, + message: exception.message, timestamp: new Date().toISOString(), path: request.url, }) diff --git a/src/guards/auth/check-user-existing.guard.ts b/src/guards/auth/check-user-existing.guard.ts deleted file mode 100644 index 1b29845..0000000 --- a/src/guards/auth/check-user-existing.guard.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { BadRequestException, CanActivate, ExecutionContext } from '@nestjs/common' -import { UsersRepository } from '../../modules/users/infrastructure/users.repository' - -export class LimitsControlGuard implements CanActivate { - constructor(private usersRepository: UsersRepository) {} - - async canActivate(context: ExecutionContext): Promise | null { - const request = context.switchToHttp().getRequest() - const email = request.body.email - const userWithExistingEmail = await this.usersRepository.findUserByEmail(email) - if (userWithExistingEmail) - throw new BadRequestException({ - message: 'email already exist', - field: 'email', - }) - return true - } -} diff --git a/src/main.ts b/src/main.ts index 5e50866..3482ee2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { NestFactory } from '@nestjs/core' import { AppModule } from './app.module' -import { BadRequestException, ValidationPipe } from '@nestjs/common' +import { BadRequestException, Logger, ValidationPipe } from '@nestjs/common' import { HttpExceptionFilter } from './exception.filter' import * as cookieParser from 'cookie-parser' import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger' @@ -30,6 +30,8 @@ async function bootstrap() { app.useGlobalFilters(new HttpExceptionFilter()) app.use(cookieParser()) await app.listen(process.env.PORT || 3000) + const logger = new Logger('NestApplication') + logger.log(`Application is running on: ${await app.getUrl()}`) } try { diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 286bb22..cb6f0db 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,46 +1,41 @@ import { + Body, Controller, Get, - Post, - Body, - UseGuards, - Request, - Response, - NotFoundException, - UnauthorizedException, - BadRequestException, - Res, HttpCode, HttpStatus, + Post, + Request, + Res, + Response, + UnauthorizedException, + UseGuards, } from '@nestjs/common' -import { AuthService } from './auth.service' import { RegistrationDto } from './dto/registration.dto' import { LocalAuthGuard } from './guards/local-auth.guard' -import { UsersService } from '../users/services/users.service' import { JwtAuthGuard } from './guards/jwt-auth.guard' import { Response as ExpressResponse } from 'express' import { JwtRefreshGuard } from './guards/jwt-refresh.guard' import { Cookies } from '../../infrastructure/decorators/cookie.decorator' +import { CommandBus } from '@nestjs/cqrs' +import { + CreateUserCommand, + GetCurrentUserDataCommand, + LogoutCommand, + RefreshTokenCommand, + ResendVerificationEmailCommand, + VerifyEmailCommand, +} from './use-cases' + @Controller('auth') export class AuthController { - constructor( - private readonly authService: AuthService, - private readonly usersService: UsersService - ) {} + constructor(private commandBus: CommandBus) {} @UseGuards(JwtAuthGuard) @Get('me') async getUserData(@Request() req) { const userId = req.user.id - const user = await this.usersService.getUserById(userId) - - if (!user) throw new UnauthorizedException() - - return { - email: user.email, - name: user.name, - id: user.id, - } + return await this.commandBus.execute(new GetCurrentUserDataCommand(userId)) } @HttpCode(200) @@ -59,32 +54,18 @@ export class AuthController { @HttpCode(201) @Post('sign-up') async registration(@Body() registrationData: RegistrationDto) { - return await this.usersService.createUser( - registrationData.name, - registrationData.password, - registrationData.email - ) + return await this.commandBus.execute(new CreateUserCommand(registrationData)) } @HttpCode(HttpStatus.OK) @Post('verify-email') async confirmRegistration(@Body('code') confirmationCode) { - const result = await this.authService.confirmEmail(confirmationCode) - if (!result) { - throw new NotFoundException() - } - return null + return await this.commandBus.execute(new VerifyEmailCommand(confirmationCode)) } @Post('resend-verification-email') - async resendRegistrationEmail(@Body('userId') userId: string) { - const isResent = await this.authService.resendCode(userId) - if (!isResent) - throw new BadRequestException({ - message: 'Email already confirmed or such email was not found', - field: 'email', - }) - return null + async resendVerificationEmail(@Body('userId') userId: string) { + return await this.commandBus.execute(new ResendVerificationEmailCommand(userId)) } @UseGuards(JwtAuthGuard) @@ -94,7 +75,7 @@ export class AuthController { @Res({ passthrough: true }) res: ExpressResponse ) { if (!refreshToken) throw new UnauthorizedException() - await this.usersService.addRevokedToken(refreshToken) + await this.commandBus.execute(new LogoutCommand(refreshToken)) res.clearCookie('refreshToken') return null } @@ -105,7 +86,7 @@ export class AuthController { async refreshToken(@Request() req, @Response({ passthrough: true }) res: ExpressResponse) { if (!req.cookies?.refreshToken) throw new UnauthorizedException() const userId = req.user.id - const newTokens = await this.authService.createJwtTokensPair(userId) + const newTokens = await this.commandBus.execute(new RefreshTokenCommand(userId)) res.cookie('refreshToken', newTokens.refreshToken, { httpOnly: true, // secure: true, diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 9286042..1a15187 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -3,11 +3,30 @@ 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 { + CreateUserHandler, + GetCurrentUserDataHandler, + LogoutHandler, + RefreshTokenHandler, + ResendVerificationEmailHandler, + VerifyEmailHandler, +} from './use-cases' +import { AuthRepository } from './infrastructure/auth.repository' + +const commandHandlers = [ + CreateUserHandler, + GetCurrentUserDataHandler, + LogoutHandler, + RefreshTokenHandler, + ResendVerificationEmailHandler, + VerifyEmailHandler, +] @Module({ - imports: [UsersModule], + imports: [UsersModule, CqrsModule], controllers: [AuthController], - providers: [AuthService, LocalStrategy], - exports: [AuthService], + providers: [AuthService, LocalStrategy, AuthRepository, ...commandHandlers], + exports: [AuthService, CqrsModule], }) export class AuthModule {} diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 1d9bc4e..1c8283c 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -10,31 +10,6 @@ import { PrismaService } from '../../prisma.service' export class AuthService { constructor(private usersRepository: UsersRepository, private prisma: PrismaService) {} - async revokeToken(token: string, userId: string): Promise { - await this.prisma.revokedToken.create({ - data: { - userId, - token, - }, - }) - } - - async isTokenRevoked(token: string): Promise { - const revokedToken = await this.prisma.revokedToken.findUnique({ - where: { token }, - }) - return !!revokedToken - } - - // Periodically remove old revoked tokens - async removeExpiredTokens(): Promise { - const hourAgo = new Date() - hourAgo.setHours(hourAgo.getHours() - 1) - await this.prisma.revokedToken.deleteMany({ - where: { revokedAt: { lt: hourAgo } }, - }) - } - async createJwtTokensPair(userId: string) { const accessSecretKey = process.env.ACCESS_JWT_SECRET_KEY const refreshSecretKey = process.env.REFRESH_JWT_SECRET_KEY @@ -62,42 +37,6 @@ export class AuthService { } } - async checkToken(accessToken: string, refreshToken: string) { - try { - await jwt.verify(accessToken, process.env.ACCESS_JWT_SECRET_KEY) - return true - } catch (err) { - if (err instanceof jwt.TokenExpiredError) { - const dbRefreshToken = await this.prisma.refreshToken.findUnique({ - where: { token: refreshToken }, - }) - const isTokenRevoked = await this.isTokenRevoked(accessToken) - if (isTokenRevoked) { - throw new UnauthorizedException() - } - if (dbRefreshToken && !dbRefreshToken.isRevoked && dbRefreshToken.expiresAt > new Date()) { - const newTokens = await this.createJwtTokensPair(dbRefreshToken.userId) - await this.prisma.refreshToken.update({ - where: { id: dbRefreshToken.id }, - data: { isRevoked: true }, - }) - return newTokens - } - } - throw err - } - } - - async logout(accessToken: string, refreshToken: string) { - // Revoke the access token - const decoded = jwt.verify(accessToken, process.env.ACCESS_JWT_SECRET_KEY) - await this.revokeToken(accessToken, decoded.userId) - await this.prisma.refreshToken.update({ - where: { token: refreshToken }, - data: { isRevoked: true }, - }) - } - async checkCredentials(email: string, password: string) { const user = await this.usersRepository.findUserByEmail(email) if (!user /*|| !user.emailConfirmation.isConfirmed*/) @@ -130,27 +69,4 @@ export class AuthService { private async isPasswordCorrect(password: string, hash: string) { return bcrypt.compare(password, hash) } - - async confirmEmail(token: string): Promise { - const verificationWithUser = await this.usersRepository.findUserByVerificationToken(token) - console.log(verificationWithUser) - if (!verificationWithUser || verificationWithUser.isEmailVerified) return false - const dbToken = verificationWithUser.verificationToken - const isTokenExpired = isBefore(verificationWithUser.verificationTokenExpiry, new Date()) - console.log({ isTokenExpired }) - if (dbToken !== token || isTokenExpired) { - return false - } - - return await this.usersRepository.updateConfirmation(verificationWithUser.userId) - } - - async resendCode(userId: string) { - const user = await this.usersRepository.findUserById(userId) - if (!user || user?.verification.isEmailVerified) return null - const updatedUser = await this.usersRepository.updateVerificationToken(user.id) - if (!updatedUser) return null - - return true - } } diff --git a/src/modules/auth/infrastructure/auth.repository.ts b/src/modules/auth/infrastructure/auth.repository.ts new file mode 100644 index 0000000..87c93d7 --- /dev/null +++ b/src/modules/auth/infrastructure/auth.repository.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common' +import { PrismaService } from '../../../prisma.service' + +@Injectable() +export class AuthRepository { + constructor(private prisma: PrismaService) {} + + async createRefreshToken(userId: string, token: string, expiresAt: Date) { + return await this.prisma.refreshToken.create({ + data: { + userId, + token, + expiresAt, + isRevoked: false, + }, + }) + } +} diff --git a/src/modules/auth/use-cases/create-user-use-case.ts b/src/modules/auth/use-cases/create-user-use-case.ts new file mode 100644 index 0000000..1882f14 --- /dev/null +++ b/src/modules/auth/use-cases/create-user-use-case.ts @@ -0,0 +1,46 @@ +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 { UsersService } from '../../users/services/users.service' + +export class CreateUserCommand { + constructor(public readonly user: { name: string; password: string; email: string }) {} +} + +@CommandHandler(CreateUserCommand) +export class CreateUserHandler implements ICommandHandler { + constructor( + private readonly usersRepository: UsersRepository, + private readonly usersService: UsersService + ) {} + + async execute(command: CreateUserCommand): Promise { + const { name, password, email } = command.user + const passwordHash = await this.usersService.generateHash(password) + const verificationToken = uuidv4() + const newUser: CreateUserInput = { + name: name || email.split('@')[0], + email: email, + password: passwordHash, + verificationToken, + verificationTokenExpiry: addHours(new Date(), 24), + isEmailVerified: false, + } + const createdUser = await this.usersRepository.createUser(newUser) + if (!createdUser) { + return null + } + await this.usersService.sendConfirmationEmail({ + email: createdUser.email, + name: createdUser.name, + verificationToken: verificationToken, + }) + return { + id: createdUser.id, + name: createdUser.name, + email: createdUser.email, + } + } +} diff --git a/src/modules/auth/use-cases/get-current-user-data-use-case.ts b/src/modules/auth/use-cases/get-current-user-data-use-case.ts new file mode 100644 index 0000000..829e0cb --- /dev/null +++ b/src/modules/auth/use-cases/get-current-user-data-use-case.ts @@ -0,0 +1,22 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { UserViewType } from '../../../types/types' +import { UnauthorizedException } from '@nestjs/common' +import { pick } from 'remeda' +import { UsersRepository } from '../../users/infrastructure/users.repository' + +export class GetCurrentUserDataCommand { + constructor(public readonly userId: string) {} +} + +@CommandHandler(GetCurrentUserDataCommand) +export class GetCurrentUserDataHandler implements ICommandHandler { + constructor(private readonly usersRepository: UsersRepository) {} + + async execute(command: GetCurrentUserDataCommand): Promise { + const user = await this.usersRepository.findUserById(command.userId) + + if (!user) throw new UnauthorizedException() + + return pick(user, ['email', 'name', 'id', 'isEmailVerified']) + } +} diff --git a/src/modules/auth/use-cases/index.ts b/src/modules/auth/use-cases/index.ts new file mode 100644 index 0000000..c9b4135 --- /dev/null +++ b/src/modules/auth/use-cases/index.ts @@ -0,0 +1,6 @@ +export * from './create-user-use-case' +export * from './get-current-user-data-use-case' +export * from './logout-use-case' +export * from './resend-verification-email-use-case' +export * from './refresh-token-use-case' +export * from './verify-email-use-case' diff --git a/src/modules/auth/use-cases/logout-use-case.ts b/src/modules/auth/use-cases/logout-use-case.ts new file mode 100644 index 0000000..89fcc2b --- /dev/null +++ b/src/modules/auth/use-cases/logout-use-case.ts @@ -0,0 +1,30 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { Logger } from '@nestjs/common' +import { UsersRepository } from '../../users/infrastructure/users.repository' +import jwt from 'jsonwebtoken' + +export class LogoutCommand { + constructor(public readonly refreshToken: string) {} +} + +@CommandHandler(LogoutCommand) +export class LogoutHandler implements ICommandHandler { + constructor(private readonly usersRepository: UsersRepository) {} + + private readonly logger = new Logger(LogoutHandler.name) + + async execute(command: 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) + } catch (e) { + this.logger.log(`Decoding error: ${e}`) + return null + } + } +} diff --git a/src/modules/auth/use-cases/refresh-token-use-case.ts b/src/modules/auth/use-cases/refresh-token-use-case.ts new file mode 100644 index 0000000..bccc823 --- /dev/null +++ b/src/modules/auth/use-cases/refresh-token-use-case.ts @@ -0,0 +1,35 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { AuthRepository } from '../infrastructure/auth.repository' +import jwt from 'jsonwebtoken' +import { addDays } from 'date-fns' + +export class RefreshTokenCommand { + constructor(public readonly userId: string) {} +} + +@CommandHandler(RefreshTokenCommand) +export class RefreshTokenHandler implements ICommandHandler { + constructor(private readonly authRepository: AuthRepository) {} + + async execute(command: RefreshTokenCommand) { + const { userId } = command + + const accessSecretKey = process.env.ACCESS_JWT_SECRET_KEY + const refreshSecretKey = process.env.REFRESH_JWT_SECRET_KEY + + const payload: { userId: string; date: Date } = { + userId, + date: new Date(), + } + const accessToken = jwt.sign(payload, accessSecretKey, { expiresIn: '10m' }) + const refreshToken = jwt.sign(payload, refreshSecretKey, { + expiresIn: '30d', + }) + const expiresIn = addDays(new Date(), 30) + await this.authRepository.createRefreshToken(userId, refreshToken, expiresIn) + return { + accessToken, + refreshToken, + } + } +} diff --git a/src/modules/auth/use-cases/resend-verification-email-use-case.ts b/src/modules/auth/use-cases/resend-verification-email-use-case.ts new file mode 100644 index 0000000..f9369c4 --- /dev/null +++ b/src/modules/auth/use-cases/resend-verification-email-use-case.ts @@ -0,0 +1,41 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { BadRequestException, NotFoundException } from '@nestjs/common' +import { UsersRepository } from '../../users/infrastructure/users.repository' +import { UsersService } from '../../users/services/users.service' + +export class ResendVerificationEmailCommand { + constructor(public readonly userId: string) {} +} + +@CommandHandler(ResendVerificationEmailCommand) +export class ResendVerificationEmailHandler + implements ICommandHandler +{ + constructor( + private readonly usersRepository: UsersRepository, + private readonly usersService: UsersService + ) {} + + async execute(command: ResendVerificationEmailCommand) { + const user = await this.usersRepository.findUserById(command.userId) + console.log(user) + if (!user) { + throw new NotFoundException('User not found') + } + if (user.isEmailVerified) { + throw new BadRequestException('Email has already been verified') + } + + const updatedUser = await this.usersRepository.updateVerificationToken(user.id) + await this.usersService.sendConfirmationEmail({ + email: updatedUser.user.email, + name: updatedUser.user.name, + verificationToken: updatedUser.verificationToken, + }) + if (!updatedUser) { + throw new NotFoundException('User not found') + } + + return null + } +} diff --git a/src/modules/auth/use-cases/verify-email-use-case.ts b/src/modules/auth/use-cases/verify-email-use-case.ts new file mode 100644 index 0000000..6500f57 --- /dev/null +++ b/src/modules/auth/use-cases/verify-email-use-case.ts @@ -0,0 +1,35 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs' +import { BadRequestException, NotFoundException } from '@nestjs/common' +import { isBefore } from 'date-fns' +import { UsersRepository } from '../../users/infrastructure/users.repository' + +export class VerifyEmailCommand { + constructor(public readonly token: string) {} +} + +@CommandHandler(VerifyEmailCommand) +export class VerifyEmailHandler implements ICommandHandler { + constructor(private readonly usersRepository: UsersRepository) {} + + async execute(command: VerifyEmailCommand) { + const token = command.token + + const verificationWithUser = await this.usersRepository.findUserByVerificationToken(token) + if (!verificationWithUser) throw new NotFoundException('User not found') + + if (verificationWithUser.isEmailVerified) + throw new BadRequestException('Email has already been verified') + + 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 + } +} diff --git a/src/modules/users/api/users.controller.ts b/src/modules/users/api/users.controller.ts index e88ace9..29298ac 100644 --- a/src/modules/users/api/users.controller.ts +++ b/src/modules/users/api/users.controller.ts @@ -13,10 +13,12 @@ import { UsersService } from '../services/users.service' import { CreateUserDto } from '../dto/create-user.dto' import { Pagination } from '../../../infrastructure/common/pagination.service' import { BaseAuthGuard } from '../../auth/guards/base-auth.guard' +import { CommandBus } from '@nestjs/cqrs' +import { CreateUserCommand } from '../../auth/use-cases' @Controller('users') export class UsersController { - constructor(private usersService: UsersService) {} + constructor(private usersService: UsersService, private commandBus: CommandBus) {} @Get() async findAll(@Query() query) { @@ -29,10 +31,12 @@ export class UsersController { //@UseGuards(BaseAuthGuard) @Post() async create(@Body() createUserDto: CreateUserDto) { - return await this.usersService.createUser( - createUserDto.login, - createUserDto.password, - createUserDto.email + return await this.commandBus.execute( + new CreateUserCommand({ + name: createUserDto.name, + email: createUserDto.email, + password: createUserDto.password, + }) ) } diff --git a/src/modules/users/dto/create-user.dto.ts b/src/modules/users/dto/create-user.dto.ts index 716368e..03050c8 100644 --- a/src/modules/users/dto/create-user.dto.ts +++ b/src/modules/users/dto/create-user.dto.ts @@ -2,7 +2,7 @@ import { Length, Matches } from 'class-validator' export class CreateUserDto { @Length(3, 10) - login: string + name: string @Length(6, 20) password: string @Matches(/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/) diff --git a/src/modules/users/infrastructure/users.repository.ts b/src/modules/users/infrastructure/users.repository.ts index 0e67bec..a504892 100644 --- a/src/modules/users/infrastructure/users.repository.ts +++ b/src/modules/users/infrastructure/users.repository.ts @@ -7,13 +7,13 @@ import { } from '../../../types/types' import { Injectable } from '@nestjs/common' import { addHours } from 'date-fns' -import { IUsersRepository } from '../services/users.service' import { v4 as uuidv4 } from 'uuid' import { PrismaService } from '../../../prisma.service' import { pick } from 'remeda' import { Prisma } from '@prisma/client' + @Injectable() -export class UsersRepository implements IUsersRepository { +export class UsersRepository { constructor(private prisma: PrismaService) {} async getUsers( @@ -123,13 +123,18 @@ export class UsersRepository implements IUsersRepository { return verification } - async updateConfirmation(id: string) { + async updateEmailVerification(id: string) { const result = await this.prisma.verification.update({ where: { userId: id, }, data: { isEmailVerified: true, + user: { + update: { + isEmailVerified: true, + }, + }, }, }) return result.isEmailVerified diff --git a/src/modules/users/services/users.service.ts b/src/modules/users/services/users.service.ts index 96a7a8a..0404e6a 100644 --- a/src/modules/users/services/users.service.ts +++ b/src/modules/users/services/users.service.ts @@ -1,6 +1,4 @@ -import { v4 as uuidv4 } from 'uuid' import { CreateUserInput, EntityWithPaginationType, User, UserViewType } from '../../../types/types' -import { addHours } from 'date-fns' import { Injectable } from '@nestjs/common' import jwt from 'jsonwebtoken' import { UsersRepository } from '../infrastructure/users.repository' @@ -19,47 +17,6 @@ export class UsersService { return await this.usersRepository.findUserById(id) } - async createUser(name: string, password: string, email: string): Promise { - const passwordHash = await this._generateHash(password) - const verificationToken = uuidv4() - const newUser: CreateUserInput = { - name: name || email.split('@')[0], - email: email, - password: passwordHash, - verificationToken, - verificationTokenExpiry: addHours(new Date(), 24), - isEmailVerified: false, - } - const createdUser = await this.usersRepository.createUser(newUser) - if (!createdUser) { - return null - } - await this.sendConfirmationEmail({ - email: createdUser.email, - name: createdUser.name, - verificationToken: verificationToken, - }) - return { - id: createdUser.id, - name: createdUser.name, - email: createdUser.email, - } - } - async resendConfirmationEmail(userId: string) { - const user = await this.usersRepository.findUserById(userId, { verification: true }) - if (!user) { - return null - } - if (user.isEmailVerified) { - return null - } - await this.sendConfirmationEmail({ - email: user.email, - name: user.name, - verificationToken: user.verification.verificationToken, - }) - return true - } async deleteUserById(id: string): Promise { return await this.usersRepository.deleteUserById(id) } @@ -68,19 +25,7 @@ export class UsersService { return await this.usersRepository.deleteAllUsers() } - async addRevokedToken(token: string) { - 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) - } catch (e) { - console.log(`Decoding error: ${e}`) - return null - } - } - private async sendConfirmationEmail({ + public async sendConfirmationEmail({ email, name, verificationToken, @@ -95,30 +40,14 @@ export class UsersService { to: email, text: 'hello and welcome, token is: ' + verificationToken, html: `Hello ${name}!
Please confirm your email by clicking on the link below:
Confirm email`, - subject: 'E-mail confirmation ', + subject: 'E-mail confirmation', }) } catch (e) { console.log(e) } } - private async _generateHash(password: string) { + + public async generateHash(password: string) { return await bcrypt.hash(password, 10) } } - -export interface IUsersRepository { - getUsers( - page: number, - pageSize: number, - searchNameTerm: string, - searchEmailTerm: string - ): Promise> - - createUser(newUser: CreateUserInput): Promise - - deleteUserById(id: string): Promise - - // findUserById(id: string): Promise - - revokeToken(id: string, token: string): Promise -} diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 13380ff..24c8185 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common' import { UsersService } from './services/users.service' import { UsersController } from './api/users.controller' import { UsersRepository } from './infrastructure/users.repository' +import { CqrsModule } from '@nestjs/cqrs' @Module({ + imports: [CqrsModule], controllers: [UsersController], providers: [UsersService, UsersRepository], - exports: [UsersRepository, UsersService], + exports: [UsersRepository, UsersService, CqrsModule], }) export class UsersModule {}