enhance register

master
aliMohtarami 4 weeks ago
parent 65e3afd64f
commit d9be3397f7
  1. 6
      .vscode/settings.json
  2. 163
      package-lock.json
  3. 1
      package.json
  4. 4
      src/shop/shop.service.ts
  5. 1
      src/users/guard/auth.guard.ts
  6. 1
      src/users/guard/jwt.strategy.ts
  7. 37
      src/users/users.controller.ts
  8. 45
      src/users/users.service.ts

@ -1,3 +1,7 @@
{ {
"cSpell.words": ["zarinpal"] "cSpell.words": [
"uuidv",
"zarinpal"
],
"CodeGPT.apiKey": "CodeGPT Plus Beta"
} }

163
package-lock.json generated

@ -21,6 +21,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"google-auth-library": "^9.15.1",
"ioredis": "^5.4.2", "ioredis": "^5.4.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
@ -3425,7 +3426,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -3462,6 +3462,15 @@
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bignumber.js": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -5242,6 +5251,12 @@
"type": "^2.7.2" "type": "^2.7.2"
} }
}, },
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/external-editor": { "node_modules/external-editor": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@ -5717,6 +5732,70 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/gaxios": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
"integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"is-stream": "^2.0.0",
"node-fetch": "^2.6.9",
"uuid": "^9.0.1"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gaxios/node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/gaxios/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/gaxios/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/gcp-metadata": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^6.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -5840,6 +5919,44 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/google-auth-library": {
"version": "9.15.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
"integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^6.1.1",
"gcp-metadata": "^6.1.0",
"gtoken": "^7.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/google-auth-library/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/google-auth-library/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -5865,6 +5982,40 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/gtoken": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
"license": "MIT",
"dependencies": {
"gaxios": "^6.0.0",
"jws": "^4.0.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/gtoken/node_modules/jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/gtoken/node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -6290,7 +6441,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -7280,6 +7430,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/json-buffer": { "node_modules/json-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",

@ -32,6 +32,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"google-auth-library": "^9.15.1",
"ioredis": "^5.4.2", "ioredis": "^5.4.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",

@ -29,7 +29,7 @@ export class ShopService {
const sandboxMode = true; const sandboxMode = true;
return ZarinpalCheckout.create(merchantId, sandboxMode); return ZarinpalCheckout.create(merchantId, sandboxMode);
} }
///////////////////////////////////////////products////////////////////////////////////////////// /////////////////////////////////////////products//////////////////////////////////////////////
// create a new product // create a new product
async create(createProductDto: CreateProductDto): Promise<Product> { async create(createProductDto: CreateProductDto): Promise<Product> {
try { try {
@ -203,7 +203,7 @@ export class ShopService {
throw new HttpException("An unexpected error occurred while deleting the product.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred while deleting the product.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
////////////////////////////////////////////cart///////////////////////////////////////////////// //////////////////////////////////////////cart/////////////////////////////////////////////////
//create and add item to a cart //create and add item to a cart
async createAndAddItemToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { async createAndAddItemToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> {
const { userId, productId, quantity } = addToCartDto; const { userId, productId, quantity } = addToCartDto;

@ -27,7 +27,6 @@ export class JwtAuthGuard implements CanActivate {
request.user = decoded; request.user = decoded;
return true; return true;
} catch (error) { } catch (error) {
console.log(error)
throw new UnauthorizedException("Invalid or expired token"); throw new UnauthorizedException("Invalid or expired token");
} }
} }

@ -16,7 +16,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
const { jti, id } = payload; const { jti, id } = payload;
const response = await this.redisService.isWhitelisted(id, jti) const response = await this.redisService.isWhitelisted(id, jti)
console.log(response)
if (!response) { if (!response) {
throw new UnauthorizedException('Token is not whitelisted'); throw new UnauthorizedException('Token is not whitelisted');
} }

@ -1,45 +1,50 @@
import { Controller, Post, Body, UseGuards, Get, Request, Put, Param, HttpException, HttpStatus, Delete, Query } from "@nestjs/common"; import { Controller, Get, Query, Post, Body, UseGuards, Request, Put, Param, HttpException, HttpStatus, Delete } from "@nestjs/common";
import { UsersService } from "./users.service"; import { UsersService } from "./users.service";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto"; import { CreateUserDto } from "./dto/create-user.dto";
import { LoginUserDto } from "./dto/login-user.dto"; import { LoginUserDto } from "./dto/login-user.dto";
import { JwtAuthGuard } from "src/users/guard/auth.guard";
import { UpdateUserDto } from "./dto/update-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto";
import { RoleGuard } from "src/users/guard/role.guard"; import { JwtAuthGuard } from "./guard/auth.guard";
import { RoleGuard } from "./guard/role.guard";
import { User } from "./entities/user.entity";
@Controller("user") @Controller("user")
export class UsersController { export class UsersController {
constructor(private readonly usersService: UsersService) {} constructor(private readonly usersService: UsersService) {}
//register // Register
@Post("register") @Post("register")
async register(@Body() createUserDto: CreateUserDto) { async register(@Body() createUserDto: CreateUserDto) {
return this.usersService.register(createUserDto); return this.usersService.register(createUserDto);
} }
//login
// Login
@Post("login") @Post("login")
async login(@Body() loginUserDto: LoginUserDto) { async login(@Body() loginUserDto: LoginUserDto) {
return this.usersService.login(loginUserDto); return this.usersService.login(loginUserDto);
} }
//get access token
// Get new access token
@Post("new-token") @Post("new-token")
async newAccessToken(@Body("token") token: string) { async newAccessToken(@Body("token") token: string) {
return this.usersService.newAccessToken(token); return this.usersService.newAccessToken(token);
} }
//logout user
// Logout user
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get("logout") @Get("logout")
async logout(@Request() req) { async logout(@Request() req) {
const user = req.user; const user = req.user;
return this.usersService.logout(user); return this.usersService.logout(user);
} }
//retrieve a user information
// Retrieve user information
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get() @Get()
async getProfile(@Request() req): Promise<User> { async getProfile(@Request() req): Promise<User> {
const userId = req.user.id; const userId = req.user.id;
return this.usersService.getProfile(userId); return this.usersService.getProfile(userId);
} }
//edit user profile
// Edit user profile
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Put() @Put()
async editProfile(@Request() req, @Body() updateUserDto: UpdateUserDto) { async editProfile(@Request() req, @Body() updateUserDto: UpdateUserDto) {
@ -47,8 +52,10 @@ export class UsersController {
const { refreshToken, ...sanitizedDto } = updateUserDto; const { refreshToken, ...sanitizedDto } = updateUserDto;
return this.usersService.editProfile(userId, sanitizedDto); return this.usersService.editProfile(userId, sanitizedDto);
} }
/////////////////////////////////////admin access endpoints/////////////////////////////////////////////////////////
//get users list (admin) ///////////////////////////////////// Admin access endpoints /////////////////////////////////////////
// Get users list (admin)
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Get('users-list') @Get('users-list')
async findAll( async findAll(
@ -57,13 +64,15 @@ export class UsersController {
){ ){
return this.usersService.findAll(page, limit); return this.usersService.findAll(page, limit);
} }
// get a specific user info (admin)
// Get specific user info (admin)
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Get("userinfo/:id") @Get("userinfo/:id")
async findSpecificUserInfoByUser(@Param("id") id) { async findSpecificUserInfoByUser(@Param("id") id) {
return this.usersService.findSpecificUserInfoByUser(id); return this.usersService.findSpecificUserInfoByUser(id);
} }
// delete a specific user (admin)
// Delete specific user (admin)
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Delete(":id?") @Delete(":id?")
async deleteUser(@Param("id") id) { async deleteUser(@Param("id") id) {

@ -1,4 +1,4 @@
import { HttpException, HttpStatus, Injectable, UnauthorizedException, BadRequestException, NotFoundException, UseGuards } from "@nestjs/common"; import { HttpException, HttpStatus, Injectable, UnauthorizedException, BadRequestException, NotFoundException } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize"; import { InjectModel } from "@nestjs/sequelize";
import { User } from "./entities/user.entity"; import { User } from "./entities/user.entity";
import * as bcrypt from "bcrypt"; import * as bcrypt from "bcrypt";
@ -9,6 +9,7 @@ import { LoginUserDto } from "./dto/login-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { RedisService } from "src/redis/redis.service"; import { RedisService } from "src/redis/redis.service";
import { Op } from "sequelize";
@Injectable() @Injectable()
export class UsersService { export class UsersService {
@ -17,45 +18,35 @@ export class UsersService {
private readonly jwtService: JwtService, private readonly jwtService: JwtService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly redisService: RedisService, private readonly redisService: RedisService,
) {} ) {
}
// Register method // Register method
async register(createUserDto: CreateUserDto) { async register(createUserDto: CreateUserDto) {
try { try {
createUserDto.password = await bcrypt.hash(createUserDto.password, parseInt(process.env.BCRYPT_SALT_ROUNDS || "10", 10)); createUserDto.password = await bcrypt.hash(createUserDto.password, parseInt(process.env.BCRYPT_SALT_ROUNDS || "10", 10));
const emailExists = await this.userModel.findOne({ const existingUser = await this.userModel.findOne({
where: { email: createUserDto.email }, where: {
}); [Op.or]: [{ email: createUserDto.email }, { phoneNumber: createUserDto.phoneNumber }, { username: createUserDto.username }],
if (emailExists) { },
throw new BadRequestException("Email is already registered.");
}
const phoneExists = await this.userModel.findOne({
where: { phoneNumber: createUserDto.phoneNumber },
}); });
if (phoneExists) {
throw new BadRequestException("Phone number is already registered.");
}
const usernameExists = await this.userModel.findOne({ if (existingUser) {
where: { username: createUserDto.username }, if (existingUser.email === createUserDto.email) {
}); throw new BadRequestException("Email is already registered.");
if (usernameExists) { }
throw new BadRequestException("Username is already registered."); if (existingUser.phoneNumber === createUserDto.phoneNumber) {
throw new BadRequestException("Phone number is already registered.");
}
if (existingUser.username === createUserDto.username) {
throw new BadRequestException("Username is already registered.");
}
} }
await this.userModel.create(createUserDto); await this.userModel.create(createUserDto);
const user = await this.userModel.findOne({ const user = await this.userModel.findOne({
where: { email: createUserDto.email }, where: { email: createUserDto.email },
}); });
const refreshToken = this.jwtService.sign(
{ id: user.id },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
return { message: "User registered successfully." }; return { message: "User registered successfully." };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {

Loading…
Cancel
Save