feat: add update profile service
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
|||||||
ValidatorConstraint,
|
ValidatorConstraint,
|
||||||
ValidatorConstraintInterface,
|
ValidatorConstraintInterface,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { I18nContext } from 'nestjs-i18n';
|
||||||
|
|
||||||
@ValidatorConstraint({ name: 'MatchFields', async: false })
|
@ValidatorConstraint({ name: 'MatchFields', async: false })
|
||||||
export class MatchFieldsConstraint implements ValidatorConstraintInterface {
|
export class MatchFieldsConstraint implements ValidatorConstraintInterface {
|
||||||
@@ -18,6 +19,14 @@ export class MatchFieldsConstraint implements ValidatorConstraintInterface {
|
|||||||
defaultMessage(args: ValidationArguments): string {
|
defaultMessage(args: ValidationArguments): string {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const [property] = args.constraints;
|
const [property] = args.constraints;
|
||||||
return `${args.property} must match ${property}`;
|
const i18n = I18nContext.current();
|
||||||
|
|
||||||
|
const field1 = i18n?.t(`users.fields.${args.property}`) ?? args.property;
|
||||||
|
const field2 = i18n?.t(`users.fields.${property}`) ?? (property as string);
|
||||||
|
|
||||||
|
return (
|
||||||
|
i18n?.t('validation.fieldsMustMatch', { args: { field1, field2 } }) ??
|
||||||
|
`${args.property} must match ${property}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
"firstName": "Firstname",
|
"firstName": "Firstname",
|
||||||
"lastName": "Lastname",
|
"lastName": "Lastname",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"password": "Password"
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"passwordPattern": "Password should contain atleast 8 characters, including letters, numbers and symbols.",
|
"passwordPattern": "Password should contain atleast 8 characters, including letters, numbers and symbols.",
|
||||||
"userNotExist": "User does not exist."
|
"userNotExist": "User does not exist."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"updateSuccess": "Updated profile info schedule."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
"requiredField": "{field} is required.",
|
"requiredField": "{field} is required.",
|
||||||
"minLengthField": "{field} should contain atleast {min} characters.",
|
"minLengthField": "{field} should contain atleast {min} characters.",
|
||||||
"maxLengthField": "{field} should contain maximum {max} characters.",
|
"maxLengthField": "{field} should contain maximum {max} characters.",
|
||||||
"wrongField": "{field} is not correct."
|
"wrongField": "{field} is not correct.",
|
||||||
|
"fieldsMustMatch": "{field1} must match {field2}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
"firstName": "نام",
|
"firstName": "نام",
|
||||||
"lastName": "نام خانوادگی",
|
"lastName": "نام خانوادگی",
|
||||||
"phone": "شماره موبایل",
|
"phone": "شماره موبایل",
|
||||||
"password": "رمز عبور"
|
"password": "رمز عبور",
|
||||||
|
"confirmPassword": "تکرار رمز عبور"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"passwordPattern": "رمز عبور باید حداقل شامل 8 کاراکتر شامل حروف، اعداد و نشانه ها باشد.",
|
"passwordPattern": "رمز عبور باید حداقل شامل 8 کاراکتر شامل حروف، اعداد و نشانه ها باشد.",
|
||||||
"userNotExist": "کاربر مورد نظر وجود ندارد."
|
"userNotExist": "کاربر مورد نظر وجود ندارد."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"updateSuccess": "اطلاعات حساب کاربری با موفقیت ثبت شد."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@
|
|||||||
"minLengthField": "{field} باید حداقل شامل {min} کاراکتر باشد.",
|
"minLengthField": "{field} باید حداقل شامل {min} کاراکتر باشد.",
|
||||||
"maxLengthField": "{field} باید حداکثر شامل {max} کاراکتر باشد.",
|
"maxLengthField": "{field} باید حداکثر شامل {max} کاراکتر باشد.",
|
||||||
"wrongField": "{field} معتبر نیست.",
|
"wrongField": "{field} معتبر نیست.",
|
||||||
"wrongFieldFormat": "فرمت {field} معتبر نیست."
|
"wrongFieldFormat": "فرمت {field} معتبر نیست.",
|
||||||
|
"fieldsMustMatch": "مقادیر {field1} و {field2} باید با هم برابر باشند."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ Content-Type: application/json
|
|||||||
lang: fa
|
lang: fa
|
||||||
|
|
||||||
{
|
{
|
||||||
"phone": "09121111114"
|
"phone": "09333026363",
|
||||||
|
"password": "rad1.3.5"
|
||||||
}
|
}
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import { Transform } from 'class-transformer';
|
|
||||||
import {
|
|
||||||
IsNotEmpty,
|
|
||||||
IsString,
|
|
||||||
Matches,
|
|
||||||
MaxLength,
|
|
||||||
MinLength,
|
|
||||||
} from 'class-validator';
|
|
||||||
import Utils from '@/common/utils';
|
|
||||||
import { i18nValidationMessage as t } from 'nestjs-i18n';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
export class CreateUserDTO {
|
|
||||||
@ApiProperty({
|
|
||||||
type: 'string',
|
|
||||||
description: `User's Firstname`,
|
|
||||||
example: 'Radmehr',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({
|
|
||||||
message: t('validation.requiredField', {
|
|
||||||
field: '$t(users.fields.firstName)',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MinLength(3, {
|
|
||||||
message: t('validation.minLengthField', {
|
|
||||||
field: '$t(users.fields.firstName)',
|
|
||||||
min: 3,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MaxLength(96, {
|
|
||||||
message: t('validation.maxLengthField', {
|
|
||||||
field: '$t(users.fields.firstName)',
|
|
||||||
max: 96,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
firstName: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: 'string',
|
|
||||||
description: `User's Lastname`,
|
|
||||||
example: 'Tarnas',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({
|
|
||||||
message: t('validation.requiredField', {
|
|
||||||
field: '$t(users.fields.lastName)',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MinLength(3, {
|
|
||||||
message: t('validation.minLengthField', {
|
|
||||||
field: '$t(users.fields.lastName)',
|
|
||||||
min: 3,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MaxLength(96, {
|
|
||||||
message: t('validation.maxLengthField', {
|
|
||||||
field: '$t(users.fields.lastName)',
|
|
||||||
min: 3,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
lastName: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: 'string',
|
|
||||||
description: `User's Phone Number`,
|
|
||||||
example: '09121111111',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({
|
|
||||||
message: t('validation.requiredField', {
|
|
||||||
field: '$t(users.fields.phone)',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@Matches(/^(?:09|9)[0-9]{9}$/, {
|
|
||||||
message: t('validation.wrongField', {
|
|
||||||
field: '$t(users.fields.phone)',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@Transform(({ value }) => Utils.StringUtils.normalizePhone(value as string))
|
|
||||||
phone: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: 'string',
|
|
||||||
description: 'Password for User Account',
|
|
||||||
example: 'Password1@',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty({
|
|
||||||
message: t('validation.requiredField', {
|
|
||||||
field: '$t(users.fields.password)',
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MinLength(8, {
|
|
||||||
message: t('validation.minLengthField', {
|
|
||||||
field: '$t(users.fields.password)',
|
|
||||||
min: 8,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@MaxLength(96, {
|
|
||||||
message: t('validation.maxLengthField', {
|
|
||||||
field: '$t(users.fields.password)',
|
|
||||||
max: 96,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@Matches(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z\d]).{8,}$/, {
|
|
||||||
message: t('users.errors.passwordPattern'),
|
|
||||||
})
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
GET http://localhost:3000/users/profile
|
GET http://localhost:3000/users/profile
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjYsInBob25lIjoiMDkxMjExMTExMTEiLCJpYXQiOjE3Nzc5NzU3NjQsImV4cCI6MTc3Nzk3OTM2NCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.m3sIOApct0GYBNQ7TuWjf7wo2ZCvu-3vPYeWEHTuHMc
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
PATCH http://localhost:3000/users/profile
|
PATCH http://localhost:3000/users/profile
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjUsInBob25lIjoiMDkxMjExMTExMTEiLCJpYXQiOjE3NzIxNzQwMzUsImV4cCI6MTc3MjE3NzYzNSwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.bux4kC-J9AV8HJpo41t8-iRGDJwVOVb0yMK9cAs4ELA
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c
|
||||||
|
lang: fa
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c",
|
||||||
|
// "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsImp0aSI6IjI3ZTk1NGI2LWRjZDktNDVkNy04OWRhLTIwYWM0NjcwZmZmNCIsImlhdCI6MTc3ODM5MTQxNCwiZXhwIjoxNzc5MjU1NDE0LCJhdWQiOiJsb2NhbGhvc3QiLCJpc3MiOiJsb2NhbGhvc3QifQ.mX3KideTda658EEi03cQGxgi4j6qwPeoCg2wr9dj_u0"
|
||||||
|
// }
|
||||||
|
|
||||||
{
|
{
|
||||||
"firstName": "Radmehr",
|
"firstName": "Radi",
|
||||||
"lastName": "Tarnas",
|
"lastName": "Tar"
|
||||||
"password": "Password1@",
|
// "password": "Password1@",
|
||||||
"confirmPassword": "ldsfjlsdf"
|
// "confirmPassword": "Password1@"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||||
|
import { Injectable, RequestTimeoutException } from '@nestjs/common';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
import { User } from '../user.entity';
|
||||||
|
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserProfileProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject User Repository
|
||||||
|
*/
|
||||||
|
@InjectRepository(User)
|
||||||
|
private readonly userRepository: Repository<User>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Hashing Provider
|
||||||
|
*/
|
||||||
|
private readonly hasingProvider: HashingProvider,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject I18n Service
|
||||||
|
*/
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public getProfile(user: User) {
|
||||||
|
return {
|
||||||
|
phone: user.phone,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
dateJoined: user.createdAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateProfile(user: User, updateProfileDto: UpdateProfileDTO) {
|
||||||
|
const { firstName, lastName, password, confirmPassword } = updateProfileDto;
|
||||||
|
const updateData: Partial<User> = {};
|
||||||
|
|
||||||
|
if (firstName !== undefined) {
|
||||||
|
updateData.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastName !== undefined) {
|
||||||
|
updateData.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
password !== undefined &&
|
||||||
|
confirmPassword !== undefined &&
|
||||||
|
password === confirmPassword
|
||||||
|
) {
|
||||||
|
updateData.password = await this.hasingProvider.hash(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.userRepository.update(user.id, updateData);
|
||||||
|
} catch (err) {
|
||||||
|
throw new RequestTimeoutException('common.errors.requestTimeout', {
|
||||||
|
cause: err,
|
||||||
|
description: 'Error connecting to the database',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: this.i18nService.translate('users.messages.updateSuccess'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,22 @@ import {
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { User } from '../user.entity';
|
import { User } from '../user.entity';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { UpdateProfileDTO } from '../dto/update-profile.dto';
|
||||||
|
import { UserProfileProvider } from './user-profile.provider';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* Injecting User Repository
|
* Inject User Repository
|
||||||
*/
|
*/
|
||||||
@InjectRepository(User)
|
@InjectRepository(User)
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject UpdateProfile Provider
|
||||||
|
*/
|
||||||
|
private readonly userProfileProvider: UserProfileProvider,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async findOneById(id: number) {
|
public async findOneById(id: number) {
|
||||||
@@ -62,11 +69,10 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getProfile(user: User) {
|
public getProfile(user: User) {
|
||||||
return {
|
return this.userProfileProvider.getProfile(user);
|
||||||
phone: user.phone,
|
}
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
public async updateProfile(user: User, updateProfileDto: UpdateProfileDTO) {
|
||||||
dateJoined: user.createdAt,
|
return await this.userProfileProvider.updateProfile(user, updateProfileDto);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { UpdateProfileDTO } from './dto/update-profile.dto';
|
|||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* Injecting Users Service
|
* Inject Users Service
|
||||||
*/
|
*/
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
) {}
|
) {}
|
||||||
@@ -19,10 +19,10 @@ export class UsersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Patch('profile')
|
@Patch('profile')
|
||||||
public updateUserProfile(
|
public async updateUserProfile(
|
||||||
@ActiveUser() user: User,
|
@ActiveUser() user: User,
|
||||||
@Body() updateProfileDto: UpdateProfileDTO,
|
@Body() updateProfileDto: UpdateProfileDTO,
|
||||||
) {
|
) {
|
||||||
return updateProfileDto;
|
return await this.usersService.updateProfile(user, updateProfileDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { UsersController } from './users.controller';
|
|||||||
import { UsersService } from './providers/users.service';
|
import { UsersService } from './providers/users.service';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { User } from './user.entity';
|
import { User } from './user.entity';
|
||||||
|
import { HashingModule } from '@/common/modules/hashing/hashing.module';
|
||||||
|
import { UserProfileProvider } from './providers/user-profile.provider';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User])],
|
imports: [TypeOrmModule.forFeature([User]), HashingModule],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService],
|
providers: [UsersService, UserProfileProvider],
|
||||||
exports: [UsersService],
|
exports: [UsersService],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|||||||
Reference in New Issue
Block a user