chore: add verfication email for user profile
This commit is contained in:
+6
-6
@@ -17,12 +17,12 @@ SMS_API_URL=https://api.sms.ir/v1
|
|||||||
SMS_API_KEY="dasdas"
|
SMS_API_KEY="dasdas"
|
||||||
SMS_PATTERN_OTP=82385
|
SMS_PATTERN_OTP=82385
|
||||||
|
|
||||||
SMTP_HOST=
|
SMTP_HOST=smtp.c1.liara.email
|
||||||
SMTP_PORT=
|
SMTP_PORT=587
|
||||||
SMTP_USER=
|
SMTP_USER=usernames
|
||||||
SMTP_PASS=
|
SMTP_PASS=password
|
||||||
|
MAIL_FROM=confirmation@asan-service.com
|
||||||
MAIL_FROM=
|
EMAIL_SECRET="secret key"
|
||||||
|
|
||||||
REDIS_URI=redis://:@localhost:6379/0
|
REDIS_URI=redis://:@localhost:6379/0
|
||||||
CACHE_TTL=120000
|
CACHE_TTL=120000
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export const enum AuthMessage {
|
|||||||
OTP_ALREADY_SENT = "کد یکبار مصرف قبلا ارسال شده است",
|
OTP_ALREADY_SENT = "کد یکبار مصرف قبلا ارسال شده است",
|
||||||
TOO_MANY_REQUESTS = "تعداد درخواست های شما بیش از حد مجاز است",
|
TOO_MANY_REQUESTS = "تعداد درخواست های شما بیش از حد مجاز است",
|
||||||
NOT_ADMIN = "شما دسترسی به بخش ادمین ندارید",
|
NOT_ADMIN = "شما دسترسی به بخش ادمین ندارید",
|
||||||
|
PHONE_SHOULD_BE_11_DIGIT = "شماره تلفن باید ۱۱ رقم باشد",
|
||||||
|
TIMESTAMP_NOT_EMPTY = "TIMESTAMP_NOT_EMPTY",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum UserMessage {
|
export const enum UserMessage {
|
||||||
@@ -66,6 +68,12 @@ export const enum UserMessage {
|
|||||||
USER_GROUP_CREATED = "گروه کاربری با موفقیت ایجاد شد",
|
USER_GROUP_CREATED = "گروه کاربری با موفقیت ایجاد شد",
|
||||||
USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID باشد",
|
USER_ID_SHOULD_BE_A_UUID = "شناسه کاربر باید یک UUID باشد",
|
||||||
NATIONAL_CODE_EXIST = "کد ملی قبلا ثبت شده است",
|
NATIONAL_CODE_EXIST = "کد ملی قبلا ثبت شده است",
|
||||||
|
PROFILE_PIC_URL = "آدرس عکس پروفایل باید یک آدرس یو آر ال باشد",
|
||||||
|
PROFILE_PIC_REQUIRED = "عکس پروفایل نمیتواند خالی باشد",
|
||||||
|
VERIFY_EMAIL_LINK_SENT = "لینک تایید ایمیل به ایمیل شما ارسال شد",
|
||||||
|
EMAIL_VERIFIED = "ایمیل شما تایید شد",
|
||||||
|
INVALID_EMAIL_TOKEN = "توکن ایمیل نامعتبر است",
|
||||||
|
EXPIRED_EMAIL_TOKEN = "توکن ایمیل منقضی شده است",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CommonMessage {
|
export const enum CommonMessage {
|
||||||
@@ -391,6 +399,7 @@ export const enum DiscountMessage {
|
|||||||
|
|
||||||
export const enum EmailMessage {
|
export const enum EmailMessage {
|
||||||
EMAIL_VERIFICATION = "تایید ایمیل",
|
EMAIL_VERIFICATION = "تایید ایمیل",
|
||||||
|
EMAIL_SENDING_FAILED = "ارسال ایمیل با خطا مواجه شد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum FinancialMessage {
|
export const enum FinancialMessage {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function mailerConfig(): MailerAsyncOptions {
|
|||||||
},
|
},
|
||||||
|
|
||||||
template: {
|
template: {
|
||||||
dir: process.cwd() + "/templates",
|
dir: process.cwd() + "/src/templates",
|
||||||
adapter: new HandlebarsAdapter(),
|
adapter: new HandlebarsAdapter(),
|
||||||
options: {
|
options: {
|
||||||
strict: true,
|
strict: true,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { AuthMessage } from "../../../common/enums/message.enum";
|
|||||||
export class CompleteRegistrationDto {
|
export class CompleteRegistrationDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||||
|
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||||
@ApiProperty({ description: "phone number", default: "09123456789" })
|
@ApiProperty({ description: "phone number", default: "09123456789" })
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { IsMobilePhone, IsNotEmpty } from "class-validator";
|
import { IsMobilePhone, IsNotEmpty, Length } from "class-validator";
|
||||||
|
|
||||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
export class RequestOtpDto {
|
export class RequestOtpDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
|
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||||
@ApiProperty({ description: "phone number", default: "09123456789" })
|
@ApiProperty({ description: "phone number", default: "09123456789" })
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { AuthMessage } from "../../../common/enums/message.enum";
|
|||||||
|
|
||||||
export class VerifyOtpDto {
|
export class VerifyOtpDto {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
|
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||||
@ApiProperty({ description: "phone number", default: "09123456789" })
|
@ApiProperty({ description: "phone number", default: "09123456789" })
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { AuthMessage } from "../../../common/enums/message.enum";
|
|||||||
export class CreateUserDto extends PartialType(CreateLegalUserDto) {
|
export class CreateUserDto extends PartialType(CreateLegalUserDto) {
|
||||||
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
@IsNotEmpty({ message: AuthMessage.PHONE_NOT_EMPTY })
|
||||||
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
@IsMobilePhone("fa-IR", {}, { message: AuthMessage.INVALID_PHONE_FORMAT })
|
||||||
|
@Length(11, 11, { message: AuthMessage.PHONE_SHOULD_BE_11_DIGIT })
|
||||||
@ApiProperty({ description: "phone number", default: "09123456789" })
|
@ApiProperty({ description: "phone number", default: "09123456789" })
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsEmail, IsNotEmpty } from "class-validator";
|
||||||
|
|
||||||
|
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class UpdateEmailDto {
|
||||||
|
@IsNotEmpty({ message: AuthMessage.EMAIL_NOT_EMPTY })
|
||||||
|
@IsEmail({}, { message: AuthMessage.INVALID_EMAIL_FORMAT })
|
||||||
|
@ApiProperty({ description: "Email", example: "ma@gmail.com" })
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty, PartialType, PickType } from "@nestjs/swagger";
|
import { ApiPropertyOptional, PartialType, PickType } from "@nestjs/swagger";
|
||||||
import { IsNotEmpty, IsOptional, IsString, Length } from "class-validator";
|
import { IsNotEmpty, IsOptional, IsString, IsUrl, Length } from "class-validator";
|
||||||
|
|
||||||
import { UserMessage } from "../../../common/enums/message.enum";
|
import { UserMessage } from "../../../common/enums/message.enum";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
||||||
@@ -9,6 +9,12 @@ export class UpdateProfileDto extends PartialType(PickType(CompleteRegistrationD
|
|||||||
@IsNotEmpty({ message: UserMessage.USERNAME_NOT_EMPTY })
|
@IsNotEmpty({ message: UserMessage.USERNAME_NOT_EMPTY })
|
||||||
@Length(3, 50, { message: UserMessage.USERNAME_SHOULD_BE_BETWEEN_3_AND_50 })
|
@Length(3, 50, { message: UserMessage.USERNAME_SHOULD_BE_BETWEEN_3_AND_50 })
|
||||||
@IsString({ message: UserMessage.USERNAME_NOT_EMPTY })
|
@IsString({ message: UserMessage.USERNAME_NOT_EMPTY })
|
||||||
@ApiProperty({ description: "User name", example: "mamad24" })
|
@ApiPropertyOptional({ description: "User name", example: "mamad24" })
|
||||||
userName?: string;
|
userName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: UserMessage.PROFILE_PIC_REQUIRED })
|
||||||
|
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: UserMessage.PROFILE_PIC_URL })
|
||||||
|
@ApiPropertyOptional({ description: "Profile picture", example: "https://www.google.com" })
|
||||||
|
profilePic?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
import { AuthMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class EmailVerifyQueryDto {
|
||||||
|
@IsNotEmpty({ message: AuthMessage.TOKEN_NOT_EMPTY })
|
||||||
|
@IsString({ message: AuthMessage.TOKEN_NOT_EMPTY })
|
||||||
|
@ApiProperty({ description: "Token", example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" })
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: UserMessage.USER_ID_SHOULD_BE_A_UUID })
|
||||||
|
@IsUUID(4, { message: UserMessage.USER_ID_SHOULD_BE_A_UUID })
|
||||||
|
@ApiProperty({ description: "User id", example: "f7b1b3b1-4b7b-4b7b-4b7b-4b7b4b7b4b7b" })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: AuthMessage.TIMESTAMP_NOT_EMPTY })
|
||||||
|
ts: string;
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ export class User extends BaseEntity {
|
|||||||
|
|
||||||
@Column({ type: "varchar", length: 100, nullable: true })
|
@Column({ type: "varchar", length: 100, nullable: true })
|
||||||
profilePic: string;
|
profilePic: string;
|
||||||
|
|
||||||
|
@Column({ type: "boolean", default: false })
|
||||||
|
emailVerified: boolean;
|
||||||
//-----------------------------------------
|
//-----------------------------------------
|
||||||
// @ManyToOne(() => Role, { eager: true, onDelete: "RESTRICT", nullable: false })
|
// @ManyToOne(() => Role, { eager: true, onDelete: "RESTRICT", nullable: false })
|
||||||
// role: Role;
|
// role: Role;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
import { createHmac } from "node:crypto";
|
||||||
|
|
||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { FastifyReply } from "fastify";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
import { In, Not, QueryRunner } from "typeorm";
|
import { In, Not, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
@@ -13,6 +17,7 @@ import {
|
|||||||
import { AddressService } from "../../address/providers/address.service";
|
import { AddressService } from "../../address/providers/address.service";
|
||||||
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto";
|
||||||
import { UserSettingsService } from "../../settings/providers/user-settings.service";
|
import { UserSettingsService } from "../../settings/providers/user-settings.service";
|
||||||
|
import { EmailService } from "../../utils/providers/email.service";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { PasswordService } from "../../utils/providers/password.service";
|
import { PasswordService } from "../../utils/providers/password.service";
|
||||||
import { WalletsService } from "../../wallets/providers/wallets.service";
|
import { WalletsService } from "../../wallets/providers/wallets.service";
|
||||||
@@ -24,8 +29,10 @@ import { CreateRoleDto } from "../DTO/create-role.dto";
|
|||||||
import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto";
|
import { SearchAdminQueryDto } from "../DTO/search-admins-query.dto";
|
||||||
import { SearchCustomersDto } from "../DTO/search-customers.dto";
|
import { SearchCustomersDto } from "../DTO/search-customers.dto";
|
||||||
import { SearchRolesQueryDto } from "../DTO/search-roles.dto";
|
import { SearchRolesQueryDto } from "../DTO/search-roles.dto";
|
||||||
|
import { UpdateEmailDto } from "../DTO/update-email.dto";
|
||||||
import { UpdateProfileDto } from "../DTO/update-profile.dto";
|
import { UpdateProfileDto } from "../DTO/update-profile.dto";
|
||||||
import { CreateUserGroupDto } from "../DTO/user-group.dto";
|
import { CreateUserGroupDto } from "../DTO/user-group.dto";
|
||||||
|
import { EmailVerifyQueryDto } from "../DTO/verify-email-query.dto";
|
||||||
import { Role } from "../entities/role.entity";
|
import { Role } from "../entities/role.entity";
|
||||||
import { User } from "../entities/user.entity";
|
import { User } from "../entities/user.entity";
|
||||||
import { RoleEnum } from "../enums/role.enum";
|
import { RoleEnum } from "../enums/role.enum";
|
||||||
@@ -50,6 +57,8 @@ export class UsersService {
|
|||||||
private readonly realUserRepository: RealUserRepository,
|
private readonly realUserRepository: RealUserRepository,
|
||||||
private readonly legalUserRepository: LegalUserRepository,
|
private readonly legalUserRepository: LegalUserRepository,
|
||||||
private readonly addressService: AddressService,
|
private readonly addressService: AddressService,
|
||||||
|
private readonly emailService: EmailService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
@@ -94,14 +103,15 @@ export class UsersService {
|
|||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
|
|
||||||
async updateProfile(userId: string, updateProfileDto: UpdateProfileDto) {
|
async updateProfile(userId: string, updateProfileDto: UpdateProfileDto) {
|
||||||
if (updateProfileDto.userName) {
|
const user = await this.userRepository.findOneBy({ id: userId });
|
||||||
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
if (updateProfileDto.userName) {
|
||||||
updateProfileDto.userName = slugify(updateProfileDto.userName, { lower: true, trim: true });
|
updateProfileDto.userName = slugify(updateProfileDto.userName, { lower: true, trim: true });
|
||||||
const existUserName = await this.userRepository.findOneBy({ userName: updateProfileDto.userName, id: Not(userId) });
|
const existUserName = await this.userRepository.findOneBy({ userName: updateProfileDto.userName, id: Not(userId) });
|
||||||
if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST);
|
if (existUserName) throw new BadRequestException(UserMessage.USERNAME_EXIST);
|
||||||
}
|
}
|
||||||
const user = await this.userRepository.findOneBy({ id: userId });
|
|
||||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
|
||||||
|
|
||||||
await this.userRepository.save({ ...user, ...updateProfileDto });
|
await this.userRepository.save({ ...user, ...updateProfileDto });
|
||||||
//
|
//
|
||||||
@@ -111,6 +121,54 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
|
|
||||||
|
async updateEmail(userId: string, updateDto: UpdateEmailDto) {
|
||||||
|
const { email } = updateDto;
|
||||||
|
const user = await this.userRepository.findOneBy({ id: userId });
|
||||||
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
//
|
||||||
|
const existEmail = await this.userRepository.findOneBy({ email, id: Not(userId) });
|
||||||
|
if (existEmail) throw new BadRequestException(UserMessage.EMAIL_EXIST);
|
||||||
|
//
|
||||||
|
const emailLink = await this.createEmailHashLink(user.id);
|
||||||
|
await this.emailService.sendVerificationEmail(email, emailLink, `${user.firstName} ${user.lastName}`);
|
||||||
|
|
||||||
|
await this.userRepository.save({ ...user, email });
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: UserMessage.VERIFY_EMAIL_LINK_SENT,
|
||||||
|
email,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/************************************************************ */
|
||||||
|
//TODO:fix this later
|
||||||
|
async verifyEmail(queryDto: EmailVerifyQueryDto, rep: FastifyReply) {
|
||||||
|
const { token, userId, ts } = queryDto;
|
||||||
|
|
||||||
|
const user = await this.userRepository.findOneBy({ id: userId });
|
||||||
|
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||||
|
|
||||||
|
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
|
||||||
|
|
||||||
|
// Check if timestamp is within allowed time window (e.g., 24 hours)
|
||||||
|
const timestamp = parseInt(ts, 10);
|
||||||
|
if (Date.now() - timestamp > 24 * 60 * 60 * 1000) {
|
||||||
|
throw new BadRequestException(UserMessage.EXPIRED_EMAIL_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = `${user.id}:${timestamp}`;
|
||||||
|
const expectedHash = createHmac("sha256", secret).update(payload).digest("hex");
|
||||||
|
|
||||||
|
if (expectedHash !== token) throw new BadRequestException(UserMessage.INVALID_EMAIL_TOKEN);
|
||||||
|
|
||||||
|
user.emailVerified = true;
|
||||||
|
await this.userRepository.save(user);
|
||||||
|
|
||||||
|
// return { message: UserMessage.EMAIL_VERIFIED };
|
||||||
|
return rep.redirect("https://google.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************************************ */
|
||||||
|
|
||||||
async findOneWithEmail(email: string): Promise<User | null> {
|
async findOneWithEmail(email: string): Promise<User | null> {
|
||||||
const user = await this.userRepository.findOneWithEmail(email);
|
const user = await this.userRepository.findOneWithEmail(email);
|
||||||
return user;
|
return user;
|
||||||
@@ -446,5 +504,20 @@ export class UsersService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createEmailHashLink(userId: string) {
|
||||||
|
const secret = this.configService.getOrThrow<string>("EMAIL_SECRET");
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const payload = `${userId}:${timestamp}`;
|
||||||
|
|
||||||
|
const hash = createHmac("sha256", secret).update(payload).digest("hex");
|
||||||
|
|
||||||
|
const url = new URL(`${this.configService.getOrThrow<string>("SITE_URL")}/users/verify-email`);
|
||||||
|
url.searchParams.append("token", hash);
|
||||||
|
url.searchParams.append("userId", userId);
|
||||||
|
url.searchParams.append("ts", timestamp.toString());
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Body, Controller, Get, HttpCode, HttpStatus, Patch, Post, Query } from "@nestjs/common";
|
import { Body, Controller, Get, HttpCode, HttpStatus, Patch, Post, Query, Res } from "@nestjs/common";
|
||||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||||
|
import { FastifyReply } from "fastify";
|
||||||
|
|
||||||
import { CheckValidityDTO } from "./DTO/check-validity.dto";
|
import { CheckValidityDTO } from "./DTO/check-validity.dto";
|
||||||
import { CreateAdminDto } from "./DTO/create-admin.dto";
|
import { CreateAdminDto } from "./DTO/create-admin.dto";
|
||||||
@@ -9,9 +10,10 @@ import { CreateRoleDto } from "./DTO/create-role.dto";
|
|||||||
import { SearchAdminQueryDto } from "./DTO/search-admins-query.dto";
|
import { SearchAdminQueryDto } from "./DTO/search-admins-query.dto";
|
||||||
import { SearchCustomersDto } from "./DTO/search-customers.dto";
|
import { SearchCustomersDto } from "./DTO/search-customers.dto";
|
||||||
import { SearchRolesQueryDto } from "./DTO/search-roles.dto";
|
import { SearchRolesQueryDto } from "./DTO/search-roles.dto";
|
||||||
|
import { UpdateEmailDto } from "./DTO/update-email.dto";
|
||||||
import { UpdateProfileDto } from "./DTO/update-profile.dto";
|
import { UpdateProfileDto } from "./DTO/update-profile.dto";
|
||||||
import { CreateUserGroupDto } from "./DTO/user-group.dto";
|
import { CreateUserGroupDto } from "./DTO/user-group.dto";
|
||||||
import { User } from "./entities/user.entity";
|
import { EmailVerifyQueryDto } from "./DTO/verify-email-query.dto";
|
||||||
import { PermissionEnum } from "./enums/permission.enum";
|
import { PermissionEnum } from "./enums/permission.enum";
|
||||||
import { UsersService } from "./providers/users.service";
|
import { UsersService } from "./providers/users.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
@@ -28,8 +30,8 @@ export class UsersController {
|
|||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@ApiOperation({ summary: "Get user profile" })
|
@ApiOperation({ summary: "Get user profile" })
|
||||||
@Get("me")
|
@Get("me")
|
||||||
getMe(@UserDec() user: User) {
|
getMe(@UserDec("id") userId: string) {
|
||||||
return this.usersService.getMe(user.id);
|
return this.usersService.getMe(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
@@ -37,8 +39,8 @@ export class UsersController {
|
|||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@ApiOperation({ summary: "Get user financial info" })
|
@ApiOperation({ summary: "Get user financial info" })
|
||||||
@Get("me/financial-info")
|
@Get("me/financial-info")
|
||||||
getFinancialInfo(@UserDec() user: User) {
|
getFinancialInfo(@UserDec("id") userId: string) {
|
||||||
return this.usersService.getMyFinancialInfo(user.id);
|
return this.usersService.getMyFinancialInfo(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
@@ -46,8 +48,22 @@ export class UsersController {
|
|||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@ApiOperation({ summary: "Update user profile" })
|
@ApiOperation({ summary: "Update user profile" })
|
||||||
@Patch("update-profile")
|
@Patch("update-profile")
|
||||||
updateProfile(@Body() updateProfileDto: UpdateProfileDto, @UserDec() user: User) {
|
updateProfile(@Body() updateProfileDto: UpdateProfileDto, @UserDec("id") userId: string) {
|
||||||
return this.usersService.updateProfile(user.id, updateProfileDto);
|
return this.usersService.updateProfile(userId, updateProfileDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AuthGuards()
|
||||||
|
@ApiOperation({ summary: "Update user email" })
|
||||||
|
@Patch("update-email")
|
||||||
|
updateEmail(@Body() updateEmailDto: UpdateEmailDto, @UserDec("id") userId: string) {
|
||||||
|
return this.usersService.updateEmail(userId, updateEmailDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Verify email" })
|
||||||
|
@HttpCode(HttpStatus.PERMANENT_REDIRECT)
|
||||||
|
@Get("verify-email")
|
||||||
|
verifyEmail(@Query() queryDto: EmailVerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) {
|
||||||
|
return this.usersService.verifyEmail(queryDto, rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
@@ -56,8 +72,8 @@ export class UsersController {
|
|||||||
@ApiOperation({ summary: "Check validity of user field" })
|
@ApiOperation({ summary: "Check validity of user field" })
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post("check-validity")
|
@Post("check-validity")
|
||||||
checkValidity(@Body() checkValidityDto: CheckValidityDTO, @UserDec() user: User) {
|
checkValidity(@Body() checkValidityDto: CheckValidityDTO, @UserDec("id") userId: string) {
|
||||||
return this.usersService.checkValidity(checkValidityDto, user.id);
|
return this.usersService.checkValidity(checkValidityDto, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************ */
|
/************************************************************ */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||||
import { MailerService } from "@nestjs-modules/mailer";
|
import { MailerService } from "@nestjs-modules/mailer";
|
||||||
|
import { SentMessageInfo } from "nodemailer";
|
||||||
|
|
||||||
import { EmailMessage } from "../../../common/enums/message.enum";
|
import { EmailMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
@@ -8,12 +9,12 @@ export class EmailService {
|
|||||||
private readonly logger = new Logger(EmailService.name);
|
private readonly logger = new Logger(EmailService.name);
|
||||||
constructor(private readonly mailerService: MailerService) {}
|
constructor(private readonly mailerService: MailerService) {}
|
||||||
|
|
||||||
async sendEmail(to: string, subject: string, link: string, fullName: string) {
|
async sendVerificationEmail(to: string, link: string, fullName: string): Promise<SentMessageInfo> {
|
||||||
try {
|
try {
|
||||||
const emailInfo = await this.mailerService.sendMail({
|
const emailInfo = await this.mailerService.sendMail({
|
||||||
to: to,
|
to: to,
|
||||||
// from: "noreply@nestjs.com",
|
|
||||||
subject: subject,
|
subject: EmailMessage.EMAIL_VERIFICATION,
|
||||||
template: "email-verify",
|
template: "email-verify",
|
||||||
context: {
|
context: {
|
||||||
title: EmailMessage.EMAIL_VERIFICATION,
|
title: EmailMessage.EMAIL_VERIFICATION,
|
||||||
@@ -24,7 +25,7 @@ export class EmailService {
|
|||||||
return emailInfo;
|
return emailInfo;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Email sending failed:", error);
|
this.logger.error("Email sending failed:", error);
|
||||||
return false;
|
throw new InternalServerErrorException(EmailMessage.EMAIL_SENDING_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,10 @@
|
|||||||
|
|
||||||
<p>به <strong>DanakCorp</strong> خوش آمدید! لطفاً برای تأیید ایمیل خود روی دکمه زیر کلیک کنید:</p>
|
<p>به <strong>DanakCorp</strong> خوش آمدید! لطفاً برای تأیید ایمیل خود روی دکمه زیر کلیک کنید:</p>
|
||||||
|
|
||||||
<a href="{{verificationLink}}" class="btn">تأیید ایمیل</a>
|
<a href="{{link}}" class="btn">تأیید ایمیل</a>
|
||||||
|
|
||||||
<p>اگر دکمه بالا کار نمیکند، میتوانید از لینک زیر استفاده کنید:</p>
|
<p>اگر دکمه بالا کار نمیکند، میتوانید از لینک زیر استفاده کنید:</p>
|
||||||
<p style="word-break: break-all"><a href="{{verificationLink}}" style="color: #4da6ff">{{verificationLink}}</a></p>
|
<p style="word-break: break-all"><a href="{{link}}" style="color: #4da6ff">{{link}}</a></p>
|
||||||
|
|
||||||
<p>اگر شما این درخواست را ارسال نکردهاید، لطفاً این ایمیل را نادیده بگیرید.</p>
|
<p>اگر شما این درخواست را ارسال نکردهاید، لطفاً این ایمیل را نادیده بگیرید.</p>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user