From 06f659101d4da5ddbfc4dfeb47d5758df1f22d74 Mon Sep 17 00:00:00 2001 From: radmehr Date: Sat, 28 Feb 2026 08:14:04 +0330 Subject: [PATCH] feat: add jwt auth guard and profile endpoint --- package-lock.json | 96 +++++++++++++++++++ package.json | 4 + .../constants/request-headers.constants.ts | 1 + .../decorators/active-user.decorator.ts | 13 +++ .../decorators/match-field.decorator.ts | 6 ++ .../validators/match-fields.validator.ts | 23 +++++ src/modules/auth/auth.controller.ts | 3 + src/modules/auth/auth.module.ts | 8 ++ src/modules/auth/constants/index.ts | 1 + .../auth/decorators/public.decorator.ts | 4 + src/modules/auth/guards/global-auth.guard.ts | 32 +++++++ src/modules/auth/guards/jwt-auth.guard.ts | 5 + .../auth/http/login.post.endpoints.http | 3 +- .../auth/http/verify-otp.post.endpoints.http | 4 +- src/modules/auth/strategies/jwt.strategy.ts | 42 ++++++++ src/modules/users/dto/update-profile.dto.ts | 94 ++++++++++++++++++ .../users/http/users.get.endpoints.http | 5 +- .../users/http/users.patch.endpoints.http | 10 ++ .../users/http/users.post.endpoints.http | 10 -- src/modules/users/providers/users.service.ts | 9 ++ src/modules/users/users.controller.ts | 29 +++--- 21 files changed, 371 insertions(+), 31 deletions(-) create mode 100644 src/common/constants/request-headers.constants.ts create mode 100644 src/common/decorators/active-user.decorator.ts create mode 100644 src/common/decorators/match-field.decorator.ts create mode 100644 src/common/validators/match-fields.validator.ts create mode 100644 src/modules/auth/constants/index.ts create mode 100644 src/modules/auth/decorators/public.decorator.ts create mode 100644 src/modules/auth/guards/global-auth.guard.ts create mode 100644 src/modules/auth/guards/jwt-auth.guard.ts create mode 100644 src/modules/auth/strategies/jwt.strategy.ts create mode 100644 src/modules/users/dto/update-profile.dto.ts create mode 100644 src/modules/users/http/users.patch.endpoints.http delete mode 100644 src/modules/users/http/users.post.endpoints.http diff --git a/package-lock.json b/package-lock.json index 5e44c63..ab030b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.6", @@ -22,6 +23,8 @@ "class-validator": "^0.14.3", "joi": "^18.0.2", "nestjs-i18n": "^10.6.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.18.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", @@ -37,6 +40,7 @@ "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.7", + "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "cross-env": "^10.1.0", "eslint": "^9.18.0", @@ -2312,6 +2316,16 @@ } } }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.14", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.14.tgz", @@ -2912,6 +2926,38 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -8111,6 +8157,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8194,6 +8276,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pg": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", @@ -10311,6 +10398,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", diff --git a/package.json b/package.json index f026aca..ae7d2c1 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.0.1", "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/schedule": "^6.1.1", "@nestjs/swagger": "^11.2.6", @@ -33,6 +34,8 @@ "class-validator": "^0.14.3", "joi": "^18.0.2", "nestjs-i18n": "^10.6.0", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.18.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", @@ -48,6 +51,7 @@ "@types/express": "^5.0.0", "@types/jest": "^29.5.14", "@types/node": "^22.10.7", + "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "cross-env": "^10.1.0", "eslint": "^9.18.0", diff --git a/src/common/constants/request-headers.constants.ts b/src/common/constants/request-headers.constants.ts new file mode 100644 index 0000000..0373445 --- /dev/null +++ b/src/common/constants/request-headers.constants.ts @@ -0,0 +1 @@ +export const REQUEST_USER_KEY = 'user'; diff --git a/src/common/decorators/active-user.decorator.ts b/src/common/decorators/active-user.decorator.ts new file mode 100644 index 0000000..8f11840 --- /dev/null +++ b/src/common/decorators/active-user.decorator.ts @@ -0,0 +1,13 @@ +import { User } from '@/modules/users/user.entity'; +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import type { Request } from 'express'; +import { REQUEST_USER_KEY } from '../constants/request-headers.constants'; + +export const ActiveUser = createParamDecorator( + (field: keyof User | undefined, ctx: ExecutionContext) => { + const request: Request = ctx.switchToHttp().getRequest(); + const user = request[REQUEST_USER_KEY] as User; + + return field ? user?.[field] : user; + }, +); diff --git a/src/common/decorators/match-field.decorator.ts b/src/common/decorators/match-field.decorator.ts new file mode 100644 index 0000000..c8d776d --- /dev/null +++ b/src/common/decorators/match-field.decorator.ts @@ -0,0 +1,6 @@ +import { Validate } from 'class-validator'; +import { MatchFieldsConstraint } from '../validators/match-fields.validator'; + +export const MatchField = (property: string) => { + return Validate(MatchFieldsConstraint, [property]); +}; diff --git a/src/common/validators/match-fields.validator.ts b/src/common/validators/match-fields.validator.ts new file mode 100644 index 0000000..17dfe8f --- /dev/null +++ b/src/common/validators/match-fields.validator.ts @@ -0,0 +1,23 @@ +import { + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ name: 'MatchFields', async: false }) +export class MatchFieldsConstraint implements ValidatorConstraintInterface { + validate(value: any, args: ValidationArguments): boolean { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const [property] = args.constraints; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const relatedValue = (args.object as any)[property]; + + return value === relatedValue; + } + + defaultMessage(args: ValidationArguments): string { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const [property] = args.constraints; + return `${args.property} must match ${property}`; + } +} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 2c88293..2d8f0d9 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, HttpCode, Post } from '@nestjs/common'; import { LoginDTO } from './dtos/login.dto'; import { AuthService } from './providers/auth.service'; import { VerifyOtpDTO } from './dtos/verify-otp.dto'; +import { Public } from './decorators/public.decorator'; @Controller('users') export class AuthController { @@ -12,12 +13,14 @@ export class AuthController { private readonly authService: AuthService, ) {} + @Public() @Post('login') @HttpCode(200) public async logIn(@Body() loginDto: LoginDTO) { return await this.authService.logIn(loginDto); } + @Public() @Post('verify-otp') @HttpCode(200) public async verifyOTP(@Body() verifyOtpDto: VerifyOtpDTO) { diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index fa90a4d..1f9aee3 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -9,6 +9,9 @@ import { ConfigService } from '@nestjs/config'; import { LoginProvider } from './providers/login.provider'; import { GenerateTokenProvider } from './providers/generate-token.provider'; import { VerifyOTPProvider } from './providers/verify-otp.provider'; +import { JwtStrategy } from './strategies/jwt.strategy'; +import { APP_GUARD } from '@nestjs/core'; +import { GlobalAuthGuard } from './guards/global-auth.guard'; @Module({ imports: [ @@ -33,6 +36,11 @@ import { VerifyOTPProvider } from './providers/verify-otp.provider'; GenerateTokenProvider, LoginProvider, VerifyOTPProvider, + JwtStrategy, + { + provide: APP_GUARD, + useClass: GlobalAuthGuard, + }, ], }) export class AuthModule {} diff --git a/src/modules/auth/constants/index.ts b/src/modules/auth/constants/index.ts new file mode 100644 index 0000000..9c16e1d --- /dev/null +++ b/src/modules/auth/constants/index.ts @@ -0,0 +1 @@ +export const IS_PUBLIC_KEY = 'isPublic'; diff --git a/src/modules/auth/decorators/public.decorator.ts b/src/modules/auth/decorators/public.decorator.ts new file mode 100644 index 0000000..215a524 --- /dev/null +++ b/src/modules/auth/decorators/public.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; +import { IS_PUBLIC_KEY } from '../constants'; + +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/modules/auth/guards/global-auth.guard.ts b/src/modules/auth/guards/global-auth.guard.ts new file mode 100644 index 0000000..3f3b139 --- /dev/null +++ b/src/modules/auth/guards/global-auth.guard.ts @@ -0,0 +1,32 @@ +import { IS_PUBLIC_KEY } from '../constants'; +import { JwtAuthGuard } from './jwt-auth.guard'; +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Observable } from 'rxjs'; + +@Injectable() +export class GlobalAuthGuard extends JwtAuthGuard { + constructor( + /** + * Inject Reflector + */ + private readonly reflector: Reflector, + ) { + super(); + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + + if (isPublic) { + return true; + } + + return super.canActivate(context); + } +} diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/src/modules/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..2155290 --- /dev/null +++ b/src/modules/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/src/modules/auth/http/login.post.endpoints.http b/src/modules/auth/http/login.post.endpoints.http index 6c13784..1b344ed 100644 --- a/src/modules/auth/http/login.post.endpoints.http +++ b/src/modules/auth/http/login.post.endpoints.http @@ -2,6 +2,5 @@ POST http://localhost:3000/users/login Content-Type: application/json { - "phone": "09126093033", - "password": "rad1.3.5" + "phone": "09121111111" } \ No newline at end of file diff --git a/src/modules/auth/http/verify-otp.post.endpoints.http b/src/modules/auth/http/verify-otp.post.endpoints.http index de10de2..4605cb8 100644 --- a/src/modules/auth/http/verify-otp.post.endpoints.http +++ b/src/modules/auth/http/verify-otp.post.endpoints.http @@ -2,6 +2,6 @@ POST http://localhost:3000/users/verify-otp Content-Type: application/json { - "phone": "09126093033", - "otp": "57903" + "phone": "09121111111", + "otp": "83793" } \ No newline at end of file diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..c4dc1c1 --- /dev/null +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,42 @@ +import jwtConfig from '@/config/jwt.config'; +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import type { ConfigType } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { AccessTokenPayload } from '../interfaces/jwt.interface'; +import { User } from '@/modules/users/user.entity'; +import { UsersService } from '@/modules/users/providers/users.service'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor( + /** + * Inject Jwt Config + */ + @Inject(jwtConfig.KEY) + private readonly jwtConfiguration: ConfigType, + + /** + * Inject Users Service + */ + private readonly usersService: UsersService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtConfiguration.access.secret || '*********', + issuer: jwtConfiguration.issuer, + audience: jwtConfiguration.audience, + }); + } + + async validate(payload: AccessTokenPayload): Promise { + const user = await this.usersService.findOneByPhone(payload.phone); + + if (!user) { + throw new UnauthorizedException(); + } + + return user; + } +} diff --git a/src/modules/users/dto/update-profile.dto.ts b/src/modules/users/dto/update-profile.dto.ts new file mode 100644 index 0000000..4b7420f --- /dev/null +++ b/src/modules/users/dto/update-profile.dto.ts @@ -0,0 +1,94 @@ +import { MatchField } from '@/common/decorators/match-field.decorator'; +import { ApiProperty } from '@nestjs/swagger'; +import { + IsOptional, + IsString, + Matches, + MaxLength, + MinLength, + ValidateIf, +} from 'class-validator'; +import { i18nValidationMessage as t } from 'nestjs-i18n'; + +export class UpdateProfileDTO { + @ApiProperty({ + type: 'string', + description: `User's Firstname`, + example: 'Radmehr', + required: false, + }) + @IsString() + @IsOptional() + @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: false, + }) + @IsString() + @IsOptional() + @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: 'Password for User Account', + example: 'Password1@', + required: false, + }) + @IsString() + @IsOptional() + @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; + + @ApiProperty({ + type: 'string', + description: 'Confirm Password of User Account (Should match Password)', + example: 'Password1@', + required: false, + }) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + @ValidateIf((o) => o.password !== undefined) + @IsOptional() + @MatchField('password') + confirmPassword?: string; +} diff --git a/src/modules/users/http/users.get.endpoints.http b/src/modules/users/http/users.get.endpoints.http index 808d954..affe635 100644 --- a/src/modules/users/http/users.get.endpoints.http +++ b/src/modules/users/http/users.get.endpoints.http @@ -1,3 +1,2 @@ -GET http://localhost:3000/users - -GET http://localhost:3000/users/2 \ No newline at end of file +GET http://localhost:3000/users/profile +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjQsInBob25lIjoiMDkxMjYxMTcwMTgiLCJpYXQiOjE3NzIxMjc1MTgsImV4cCI6MTc3MjEzMTExOCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.oaxzSd0DDakCDMiW129Ou3MJh0oT0zJTFerZEjnQ7Ug diff --git a/src/modules/users/http/users.patch.endpoints.http b/src/modules/users/http/users.patch.endpoints.http new file mode 100644 index 0000000..4f115c4 --- /dev/null +++ b/src/modules/users/http/users.patch.endpoints.http @@ -0,0 +1,10 @@ +PATCH http://localhost:3000/users/profile +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjUsInBob25lIjoiMDkxMjExMTExMTEiLCJpYXQiOjE3NzIxNzQwMzUsImV4cCI6MTc3MjE3NzYzNSwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.bux4kC-J9AV8HJpo41t8-iRGDJwVOVb0yMK9cAs4ELA + +{ + "firstName": "Radmehr", + "lastName": "Tarnas", + "password": "Password1@", + "confirmPassword": "ldsfjlsdf" +} \ No newline at end of file diff --git a/src/modules/users/http/users.post.endpoints.http b/src/modules/users/http/users.post.endpoints.http deleted file mode 100644 index ad9bc82..0000000 --- a/src/modules/users/http/users.post.endpoints.http +++ /dev/null @@ -1,10 +0,0 @@ -POST http://localhost:3000/users -Content-Type: application/json -lang: fa - -{ - "firstName": "Ra", - "lastName": "Tnas", - "phone": "9333026363", - "password": "ra" -} \ No newline at end of file diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 20e0cca..b3fd433 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -38,4 +38,13 @@ export class UsersService { return newUser; } + + public getProfile(user: User) { + return { + phone: user.phone, + firstName: user.firstName, + lastName: user.lastName, + dateJoined: user.createdAt, + }; + } } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 4fdbc99..a09c43c 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -1,5 +1,8 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, Patch } from '@nestjs/common'; import { UsersService } from './providers/users.service'; +import { ActiveUser } from '@/common/decorators/active-user.decorator'; +import { User } from './user.entity'; +import { UpdateProfileDTO } from './dto/update-profile.dto'; @Controller('users') export class UsersController { @@ -10,18 +13,16 @@ export class UsersController { private readonly usersService: UsersService, ) {} - // @Get() - // public getAllUsers() { - // return ['user1', 'user2']; - // } + @Get('profile') + public getUserProfile(@ActiveUser() user: User) { + return this.usersService.getProfile(user); + } - // @Get(':id') - // public getUserById(@Param('id', ParseIntPipe) id: number) { - // return `user ${id}`; - // } - - // @Post() - // public async createUser(@Body() createUserDto: CreateUserDTO) { - // return await this.usersService.createOne(createUserDto); - // } + @Patch('profile') + public updateUserProfile( + @ActiveUser() user: User, + @Body() updateProfileDto: UpdateProfileDTO, + ) { + return updateProfileDto; + } }