Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/email-verification/dto/verify-email.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {IsNotEmpty, IsString } from 'class-validator';

export class VerifyEmailDto {
@IsString()
@IsNotEmpty()
token: string;

}
14 changes: 11 additions & 3 deletions src/email-verification/email-verification.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { RequestEmailDto } from './dto/request-email.dto';
import { VerifyEmailDto } from './dto/verify-email.dto';
import { EmailVerificationService } from './email-verification.service';
import { Body, Controller, Post } from '@nestjs/common';

@Controller('email-verification')
export class EmailVerificationController {
constructor(private readonly emailVerificationService: EmailVerificationService) {}

@Post('send')
async SendEmail(@Body() dto: RequestEmailDto) {
await this.emailVerificationService.SendEmail(dto.email);
@Post('send-email')
async sendEmail(@Body() dto: RequestEmailDto) {
await this.emailVerificationService.sendEmail(dto.email);
return { message: 'Verification link sent to your email' };
}

@Post('verify-email')
async verifyEmail(@Body() dto: VerifyEmailDto) {
const email = await this.emailVerificationService.decodeVerificationToken(dto.token);
await this.emailVerificationService.verifyEmail(email);
return { message: 'The email is verified' };
}
}
3 changes: 2 additions & 1 deletion src/email-verification/email-verification.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { EmailVerificationController } from './email-verification.controller';
import { EmailVerificationService } from './email-verification.service';
import { User } from '../user/entities/user.entity';
import { JwtService } from '@nestjs/jwt';
import { UserService } from 'user/user.service';

@Module({
imports: [TypeOrmModule.forFeature([EmailVerification, User])],
controllers: [EmailVerificationController],
providers: [EmailVerificationService, JwtService],
providers: [EmailVerificationService, JwtService, UserService],
})
export class EmailVerificationModule {}
28 changes: 26 additions & 2 deletions src/email-verification/email-verification.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UserService } from './../user/user.service';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EmailVerification } from './entities/email-verification.entity';
Expand All @@ -16,9 +17,10 @@ export class EmailVerificationService {
private readonly userRepo: Repository<User>,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
private readonly userService: UserService,
) {}

public async SendEmail(email: string) {
public async sendEmail(email: string) {
const user = await this.userRepo.findOneBy({ email });
if (!user) throw new Error('User not found');

Expand Down Expand Up @@ -55,4 +57,26 @@ export class EmailVerificationService {

await this.emailVerificationRepo.save({ user, token, expires_at });
}

public async verifyEmail(email: string) {
await this.userService.confirmEmail(email);
}

public async decodeVerificationToken(token: string) {
try {
const payload = await this.jwtService.verify(token, {
secret: this.configService.get('JWT_SECRET'),
});

if (typeof payload === 'object' && 'email' in payload) {
return payload.email;
}
throw new BadRequestException('Invalid token payload structure');
} catch (error) {
if (error?.name === 'TokenExpiredError') {
throw new BadRequestException('Your email confirmation token has expired');
}
}
throw new BadRequestException('Failed to verify email token');
}
}
2 changes: 1 addition & 1 deletion src/migrations/1760485409896-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
export class Users1760485409896 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "users" (
`CREATE TABLE IF NOT EXISTS "users" (
"id" uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
"name" VARCHAR(255) NOT NULL,
"username" VARCHAR(255) UNIQUE NOT NULL,
Expand Down
24 changes: 22 additions & 2 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
import { Injectable } from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UserService {}
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepo: Repository<User>,
) {}

async confirmEmail(email: string) {
const user = await this.userRepo.findOne({ where: { email } });
if (!user) throw new BadRequestException('User not found');

if (user.verified_at) {
return 'This user has already been verified';
}

user.verified_at = new Date();
await this.userRepo.save(user);
}
}