Compare commits

...

2 Commits

  1. 3
      migrations/20250104074851-create-user.js
  2. 68
      migrations/20250104094702-create-admin.js
  3. 40
      src/admin/admin.controller.ts
  4. 27
      src/admin/admin.module.ts
  5. 163
      src/admin/admin.service.ts
  6. 29
      src/admin/dto/create-Admin.dto.ts
  7. 15
      src/admin/dto/login-Admin.dto.ts
  8. 33
      src/admin/dto/update-user.dto.ts
  9. 40
      src/admin/entities/admin.entity.ts
  10. 4
      src/app.controller.ts
  11. 2
      src/app.module.ts
  12. 37
      src/cart/cart.service.ts
  13. 14
      src/payment/payment.controller.ts
  14. 26
      src/payment/payment.service.ts
  15. 4
      src/users/dto/create-user.dto.ts
  16. 9
      src/users/dto/update-user.dto.ts
  17. 5
      src/users/entities/user.entity.ts
  18. 24
      src/users/users.controller.ts
  19. 111
      src/users/users.service.ts
  20. 97
      src/wallet/WalletService.ts
  21. 2
      src/wallet/wallet.controller.ts
  22. 6
      src/wallet/wallet.module.ts
  23. 3
      src/wallet/wallet.service.ts

@ -20,9 +20,8 @@ module.exports = {
allowNull: false,
},
role: {
type: Sequelize.STRING,
type: Sequelize.ENUM('admin', 'user'),
allowNull: false,
defaultValue: 'user',
},
firstName: {
type: Sequelize.STRING,

@ -1,68 +0,0 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Admins', { cascade: true });
await queryInterface.createTable('Admins', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
role: {
type: Sequelize.STRING,
defaultValue: 'admin',
},
firstName: {
type: Sequelize.STRING,
allowNull: false,
},
lastName: {
type: Sequelize.STRING,
allowNull: false,
},
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
phoneNumber: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
refreshToken: {
type: Sequelize.STRING,
allowNull: true,
},
gender: {
type: Sequelize.ENUM('male', 'female'),
allowNull: false,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('NOW'),
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Admins');
},
};

@ -1,40 +0,0 @@
import { Controller, Get, Post, Body, Request, Put, UseGuards } from "@nestjs/common";
import { AdminService } from "./admin.service";
import { CreateAdminDto } from "./dto/create-Admin.dto";
import { LoginAdminDto } from "./dto/login-Admin.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import { RoleGuard } from "src/guard/role.guard";
@Controller("admin")
export class AdminController {
constructor(private readonly adminService: AdminService) {}
// register as a admin
@Post("register")
async register(@Body() createAdminDto: CreateAdminDto): Promise<{ message }> {
return this.adminService.register(createAdminDto);
}
//login as admin
@Post("login")
async login(@Body() loginAdminDto: LoginAdminDto): Promise<{ accessToken; refreshToken }> {
return this.adminService.login(loginAdminDto);
}
//logout user
@UseGuards(RoleGuard)
@Get("logout")
async logout(@Request() req) {
const userId = req.user.id;
return this.adminService.logout(userId);
}
//get a new access token
@Post("new-token")
async newAccessToken(@Body("token") token: string) {
return this.adminService.newAccessToken(token);
}
//edit admin profile
@UseGuards(RoleGuard)
@Put()
async editAdminProfile(@Request() req, @Body() updateAdminDto: UpdateUserDto): Promise<{ message }> {
const userId = req.user.id;
return this.adminService.editAdminProfile(userId, updateAdminDto);
}
}

@ -1,27 +0,0 @@
import { Module } from '@nestjs/common';
import { AdminService } from './admin.service';
import { AdminController } from './admin.controller';
import { SequelizeModule } from '@nestjs/sequelize';
import { Admin } from './entities/admin.entity';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports:[SequelizeModule.forFeature([Admin]),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
secret: config.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: config.get<string | number>('JWT_EXPIRES', '1h'),
},
};
},
}),
],
controllers: [AdminController],
providers: [AdminService],
})
export class AdminModule {}

@ -1,163 +0,0 @@
import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize";
import { Admin } from "./entities/admin.entity";
import * as bcrypt from "bcrypt";
import { JwtService } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
import { CreateAdminDto } from "./dto/create-Admin.dto";
import { LoginAdminDto } from "./dto/login-Admin.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
@Injectable()
export class AdminService {
constructor(
@InjectModel(Admin) private readonly adminModel: typeof Admin,
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
//register method
async register(createAdminDto: CreateAdminDto): Promise<{ message }> {
try {
const existingAdminByEmail = await this.adminModel.findOne({
where: { email: createAdminDto.email },
});
if (existingAdminByEmail) {
throw new HttpException("The provided email is already registered.", HttpStatus.CONFLICT);
}
const existingAdminByUsername = await this.adminModel.findOne({
where: { username: createAdminDto.username },
});
if (existingAdminByUsername) {
throw new HttpException("The provided username is already taken.", HttpStatus.CONFLICT);
}
const existingAdminByPhoneNumber = await this.adminModel.findOne({
where: { phoneNumber: createAdminDto.phoneNumber },
});
if (existingAdminByPhoneNumber) {
throw new HttpException("The provided phone number is already registered.", HttpStatus.CONFLICT);
}
createAdminDto.password = await bcrypt.hash(createAdminDto.password, 10);
await this.adminModel.create(createAdminDto);
return { message: "Admin registration is successful." };
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//login method
async login(loginAdminDto: LoginAdminDto): Promise<{ accessToken: string; refreshToken: string }> {
try {
const admin = await this.adminModel.findOne({
where: { email: loginAdminDto.email,username:loginAdminDto.username },
});
if (!admin) {
throw new HttpException("Invalid email, username or password.", HttpStatus.UNAUTHORIZED);
}
const isValidPassword = await bcrypt.compare(loginAdminDto.password, admin.password);
if (!isValidPassword) {
throw new HttpException("Invalid email or password.", HttpStatus.UNAUTHORIZED);
}
const accessToken = this.jwtService.sign(
{ id: admin.id, role: admin.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
const refreshToken = this.jwtService.sign(
{ id: admin.id, role: admin.role },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
await this.adminModel.update(
{ refreshToken }, //
{ where: { id: admin.id } },
);
return { accessToken, refreshToken };
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//logout (delete refresh token from database)
async logout(userId: number): Promise<{ message: string }> {
try {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
const user = await this.adminModel.findOne({ where: { id: userId } });
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
await this.adminModel.update({ refreshToken: null }, { where: { id: userId } });
return { message: "Logout is successful" };
} catch (error) {
throw new HttpException("An unexpected error occurred during logout. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//getting new access token
async newAccessToken(refreshToken: string) {
if (!refreshToken) {
throw new HttpException("Refresh token is required.", HttpStatus.BAD_REQUEST);
}
let decoded;
try {
decoded = this.jwtService.verify(refreshToken, { secret: this.configService.get<string>("JWT_REFRESH_SECRET") });
} catch (error) {
throw new HttpException("Invalid or expired token.", HttpStatus.UNAUTHORIZED);
}
const user = await this.adminModel.findOne({where:{id:decoded.id}});
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
if (user.refreshToken !== refreshToken) {
throw new HttpException("Invalid refresh token.", HttpStatus.FORBIDDEN);
}
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
return { accessToken };
}
//edit admin profile method
async editAdminProfile(userId: number, updateAdminDto: UpdateUserDto) {
try {
let user = await this.adminModel.findOne({ where: { id: userId } });
if (!user) {
throw new Error("Admin not found");
}
await user.update(updateAdminDto);
user = await this.adminModel.findOne({
where: { id: userId },
attributes: { exclude: ["password"] },
});
return { message: "user account updated successful", user };
} catch (error) {
throw new Error(`An error occurred while updating admin: ${error.message}`);
}
}
}

@ -1,29 +0,0 @@
import { IsString, IsEmail, IsEnum, IsNotEmpty, IsOptional, Matches } from "class-validator";
export class CreateAdminDto {
@IsEmail({}, { message: "Invalid email format" })
email: string;
@IsString()
@IsNotEmpty({ message: "Password is required" })
password: string;
@IsString()
@IsNotEmpty({ message: "First name is required" })
firstName: string;
@IsString()
@IsNotEmpty({ message: "Last name is required" })
lastName: string;
@IsString()
@IsNotEmpty({ message: "Username is required" })
username: string;
@IsString()
@Matches(/^[0-9]{11}$/, { message: "Phone number must be 10 digits" })
phoneNumber: string;
@IsEnum(["male", "female"], { message: "Gender must be 'male' or 'female'" })
gender: string;
}

@ -1,15 +0,0 @@
import { IsString, IsEmail,IsNotEmpty} from "class-validator";
export class LoginAdminDto {
@IsEmail({}, { message: "Invalid email format" })
email: string;
@IsString()
@IsNotEmpty({ message: "Password is required" })
password: string;
@IsString()
@IsNotEmpty({ message: "Username is required" })
username: string;
}

@ -1,33 +0,0 @@
import { IsOptional, IsString, IsEmail, IsEnum } from 'class-validator';
import {Gender } from '../entities/admin.entity';
export class UpdateUserDto {
@IsOptional()
@IsString()
username?: string;
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsString()
password?: string;
@IsOptional()
@IsString()
firstName?: string;
@IsOptional()
@IsString()
lastName?: string;
@IsOptional()
@IsString()
phoneNumber?: string;
@IsOptional()
@IsEnum(Gender)
gender?: Gender;
}

@ -1,40 +0,0 @@
import { Column, Table, Model, DataType } from "sequelize-typescript";
@Table
export class Admin extends Model<Admin> {
@Column({ unique: true })
email: string;
@Column
password: string;
@Column({ defaultValue: "admin" })
role: string;
@Column
firstName: string;
@Column
lastName: string;
@Column({ unique: true })
username: string;
@Column({ unique: true })
phoneNumber: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
refreshToken: string;
@Column({
type: DataType.ENUM("male", "female"),
allowNull: false,
})
gender: string;
}
export enum Gender {
Male = "male",
Female = "female",
}

@ -1,5 +1,5 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller()
export class AppController {

@ -9,7 +9,6 @@ import { ProductsModule } from './products/products.module';
import { CartModule } from './cart/cart.module';
import { WalletModule } from './wallet/wallet.module';
import { InvoiceModule } from './invoice/invoice.module';
import { AdminModule } from './admin/admin.module';
import { PaymentModule } from "./payment/payment.module";
@Module({
@ -23,7 +22,6 @@ import { PaymentModule } from "./payment/payment.module";
CartModule,
WalletModule,
InvoiceModule,
AdminModule,
PaymentModule,

@ -2,7 +2,7 @@ import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from "@nest
import { InjectModel } from "@nestjs/sequelize";
import { Cart } from "./entities/cart.entity";
import { Product } from "src/products/entities/product.entity";
import { WalletService } from "src/wallet/wallet.service";
import { WalletService } from "src/wallet/WalletService";
import { InvoiceService } from "src/invoice/invoice.service";
import { Invoice } from "src/invoice/entities/invoice.entity";
@ -61,7 +61,6 @@ export class CartService {
cartItem: cart,
};
} catch (error) {
console.log(error)
throw new HttpException("An unexpected error occurred while adding the product to cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@ -70,54 +69,51 @@ export class CartService {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
try {
const cartItems = await this.cartModel.findAll({
where: { userId, status: "open" },
include: [
{
model: Product,
attributes: ["name", "price"],
attributes: ["name", "price"],
},
],
});
if (!cartItems || cartItems.length === 0) {
return { cartItems: [], totalPrice: 0 };
}
const totalPrice = cartItems.reduce((sum, item) => {
return sum + (Number(item.productPrice) * item.quantity || 0);
}, 0);
return { cartItems, totalPrice };
} catch (error) {
console.error("Error fetching cart items:", error);
throw new HttpException(
"An unexpected error occurred while fetching the cart. Please try again later.",
HttpStatus.INTERNAL_SERVER_ERROR
);
throw new HttpException("An unexpected error occurred while fetching the cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// Update cart item quantity
async updateCart(userId: number, productId: number, quantity: number): Promise<Cart> {
const cartItem = await this.cartModel.findOne({ where: { userId, productId, status: "open" } });
if (!cartItem) {
throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND);
}
const product = await this.productModel.findByPk(productId);
if (!product) {
throw new HttpException("Product not found.", HttpStatus.NOT_FOUND);
}
if (product.quantity < quantity) {
throw new HttpException("Insufficient product quantity.", HttpStatus.CONFLICT);
}
try {
cartItem.quantity = quantity;
await cartItem.save();
@ -127,7 +123,7 @@ export class CartService {
throw new HttpException("An unexpected error occurred while updating the cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// Remove an item from cart
async removeFromCart(userId: number, productId: number): Promise<{ message: string; cartItem: Cart }> {
const cartItem = await this.cartModel.findOne({ where: { userId, productId, status: "open" } });
@ -146,12 +142,11 @@ export class CartService {
//delete whole cart by user
async clearCart(userId: number) {
await this.cartModel.destroy({
where: { userId, status: 'open' },
where: { userId, status: "open" },
});
return { message: "Cart cleared successfully" };
}
//order
async processOrder(userId: number, totalAmount: number): Promise<{ message: string; invoice: Invoice }> {
try {

@ -1,7 +1,7 @@
import { Controller, Post, Body, Param, Get, Query, UseGuards, Request } from "@nestjs/common";
import { PaymentService } from "./payment.service";
import { InvoiceService } from "../invoice/invoice.service";
import { WalletService } from "src/wallet/wallet.service";
import { WalletService } from "src/wallet/WalletService";
import { console } from "inspector";
import { InjectModel } from "@nestjs/sequelize";
import { Payment } from "./entities/payment.entity";
@ -17,7 +17,6 @@ export class PaymentController {
private readonly paymentService: PaymentService,
private readonly invoiceService: InvoiceService,
@InjectModel(Transaction) private readonly transactionModel: typeof Transaction,
) {}
@UseGuards(JwtAuthGuard)
@ -26,8 +25,8 @@ export class PaymentController {
const userId = req.user.id;
const invoice = await this.invoiceService.getInvoicePendingByUser(userId);
const totalAmount = invoice.totalPaymentAmount;
if(totalAmount<1000){
return{message:'please enter amount above 1000'}
if (totalAmount < 1000) {
return { message: "please enter amount above 1000" };
}
const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}&amount=${totalAmount}`;
const paymentUrl = await this.paymentService.requestPayment(totalAmount, "Purchase products", callbackUrl);
@ -58,9 +57,9 @@ export class PaymentController {
status: "completed",
});
await this.transactionModel.create({
walletId:(await wallet).walletId,
amount:(String(amount).startsWith('+') ? String(amount) : `+${amount}`)
})
walletId: (await wallet).walletId,
amount: String(amount).startsWith("+") ? String(amount) : `+${amount}`,
});
return { message: "Payment successful", refId };
} catch (error) {
console.log(error);
@ -73,5 +72,4 @@ export class PaymentController {
throw new Error(`Error during payment verification: ${error.message}`);
}
}
}

@ -1,21 +1,20 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Payment } from './entities/payment.entity';
import { Injectable, InternalServerErrorException } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize";
import { Payment } from "./entities/payment.entity";
const ZarinpalCheckout = require('zarinpal-checkout');
const ZarinpalCheckout = require("zarinpal-checkout");
@Injectable()
export class PaymentService {
private zarinpal;
constructor(
) {
constructor() {
this.zarinpal = this.initializeZarinpal();
}
private initializeZarinpal() {
const merchantId = '00000000-0000-0000-0000-000000000000'; // Merchant ID should be valid
const sandboxMode = true;
const merchantId = "00000000-0000-0000-0000-000000000000"; // Merchant ID should be valid
const sandboxMode = true;
return ZarinpalCheckout.create(merchantId, sandboxMode);
}
@ -26,17 +25,17 @@ export class PaymentService {
CallbackURL: callbackUrl,
Description: description,
});
if (result.status === 100) {
return result.url;
return result.url;
} else {
throw new Error(`Payment request failed with status: ${result.status}`);
}
} catch (error) {
console.log('Error in PaymentRequest:', error.message || error);
console.log("Error in PaymentRequest:", error.message || error);
throw new InternalServerErrorException(`Error in payment request: ${error.message}`);
}
}
}
async verifyPayment(authority: string, amount: number): Promise<string> {
try {
@ -45,7 +44,7 @@ export class PaymentService {
Authority: authority,
});
if (result.status === 100) {
return result.RefID;
return result.RefID;
} else {
throw new Error(`Payment verification failed with status: ${result.status}`);
}
@ -54,5 +53,4 @@ export class PaymentService {
}
}
}

@ -8,6 +8,10 @@ export class CreateUserDto {
@IsNotEmpty({ message: "Password is required" })
password: string;
@IsString()
@IsNotEmpty({ message: "Password is required" })
role: string;
@IsString()
@IsNotEmpty({ message: "First name is required" })
firstName: string;

@ -1,6 +1,7 @@
import { IsOptional, IsString, IsEmail, IsEnum } from 'class-validator';
import {Gender } from '../entities/user.entity';
import { Exclude } from 'class-transformer';
export class UpdateUserDto {
@IsOptional()
@ -15,6 +16,10 @@ export class UpdateUserDto {
@IsString()
password?: string;
@IsOptional()
@IsString()
role?: string;
@IsOptional()
@IsString()
firstName?: string;
@ -30,4 +35,8 @@ export class UpdateUserDto {
@IsOptional()
@IsEnum(Gender)
gender?: Gender;
@Exclude()
@IsOptional()
refreshToken?: string;
}

@ -8,7 +8,10 @@ export class User extends Model<User> {
@Column
password: string;
@Column({ defaultValue: "user" })
@Column({
type: DataType.ENUM("admin", "user"),
allowNull: false,
})
role: string;
@Column

@ -1,4 +1,4 @@
import { Controller, Post, Body, UseGuards, Get, Request, Put, Param, HttpException, HttpStatus, Delete } from "@nestjs/common";
import { Controller, Post, Body, UseGuards, Get, Request, Put, Param, HttpException, HttpStatus, Delete, Query } from "@nestjs/common";
import { UsersService } from "./users.service";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
@ -6,18 +6,19 @@ import { LoginUserDto } from "./dto/login-user.dto";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { UpdateUserDto } from "./dto/update-user.dto";
import { RoleGuard } from "src/guard/role.guard";
import { plainToInstance } from "class-transformer";
@Controller("user")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
//register as user
@Post("register")
async register(@Body() createUserDto: CreateUserDto): Promise<{ message }> {
async register(@Body() createUserDto: CreateUserDto) {
return this.usersService.register(createUserDto);
}
//login as user
@Post("login")
async login(@Body() loginUserDto: LoginUserDto): Promise<{ accessToken; refreshToken }> {
async login(@Body() loginUserDto: LoginUserDto) {
return this.usersService.login(loginUserDto);
}
//get access token
@ -44,19 +45,24 @@ export class UsersController {
@Put()
async editProfile(@Request() req, @Body() updateUserDto: UpdateUserDto) {
const userId = req.user.id;
return this.usersService.editProfile(userId, updateUserDto);
const { refreshToken, ...sanitizedDto } = updateUserDto;
return this.usersService.editProfile(userId, sanitizedDto);
}
/////////////////////////////////////admin access endpoints/////////////////////////////////////////////////////////
//get users list (admin)
@UseGuards(RoleGuard)
@Get("users")
async findAll(): Promise<User[]> {
return this.usersService.findAll();
@Get('users-list')
async findAll(
@Query("page") page: number = 1, // Default page is 1
@Query("limit") limit: number = 10, // Default limit is 10
){
return this.usersService.findAll(page, limit);
}
// get a specific user info (admin)
@UseGuards(RoleGuard)
@Get("users/:id")
async findSpecificUserInfoByUser(@Param("id") id): Promise<User> {
@Get("userinfo/:id")
async findSpecificUserInfoByUser(@Param("id") id) {
return this.usersService.findSpecificUserInfoByUser(id);
}
// delete a specific user (admin)

@ -7,6 +7,8 @@ import { ConfigService } from "@nestjs/config";
import { CreateUserDto } from "./dto/create-user.dto";
import { LoginUserDto } from "./dto/login-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import e from "express";
import { IsInstance } from "class-validator";
@Injectable()
export class UsersService {
@ -17,36 +19,36 @@ export class UsersService {
) {}
// Register method
async register(createUserDto: CreateUserDto): Promise<{ message: string }> {
async register(createUserDto: CreateUserDto) {
try {
createUserDto.password = await bcrypt.hash(createUserDto.password, parseInt(process.env.BCRYPT_SALT_ROUNDS || "10", 10));
const emailExists = await this.userModel.findOne({
where: { email: createUserDto.email },
});
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({
where: { username: createUserDto.username },
});
if (usernameExists) {
throw new BadRequestException("Username is already registered.");
}
await this.userModel.create(createUserDto);
const user = await this.userModel.findOne({
where: { email: createUserDto.email },
});
const refreshToken = this.jwtService.sign(
{ id: user.id },
{
@ -54,12 +56,9 @@ export class UsersService {
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
await this.userModel.update(
{ refreshToken },
{ where: { id: user.id } },
);
await this.userModel.update({ refreshToken }, { where: { id: user.id } });
return { message: "User registered successfully." };
} catch (error) {
if (error instanceof HttpException) {
@ -69,10 +68,10 @@ export class UsersService {
}
}
// Login method
async login(loginUserDto: LoginUserDto): Promise<{ accessToken: string , refreshToken:string}> {
async login(loginUserDto: LoginUserDto) {
try {
const user = await this.userModel.findOne({
where: { email: loginUserDto.email, username:loginUserDto.username },
where: { email: loginUserDto.email, username: loginUserDto.username },
});
if (!user) {
@ -146,7 +145,7 @@ export class UsersService {
return { accessToken };
}
//logout (delete refresh token from database)
async logout(userId: number): Promise<{ message: string }> {
async logout(userId: number) {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
@ -165,7 +164,7 @@ export class UsersService {
}
}
//get information user method
async getProfile(userId: number): Promise<User> {
async getProfile(userId: number) {
try {
const user = await this.userModel.findOne({
where: { id: userId },
@ -176,6 +175,9 @@ export class UsersService {
}
return user;
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@ -186,41 +188,97 @@ export class UsersService {
if (!user) {
throw new NotFoundException("User not found.");
}
if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
const { refreshToken, ...allowedUpdates } = updateUserDto;
if (allowedUpdates.username) {
const usernameExists = await this.userModel.findOne({
where: { username: allowedUpdates.username },
});
if (usernameExists && usernameExists.id !== userId) {
throw new BadRequestException("Username is already in use.");
}
}
if (allowedUpdates.phoneNumber) {
const phoneNumberExists = await this.userModel.findOne({
where: { phoneNumber: allowedUpdates.phoneNumber },
});
if (phoneNumberExists && phoneNumberExists.id !== userId) {
throw new BadRequestException("Phone number is already in use.");
}
}
if (allowedUpdates.email) {
const emailExists = await this.userModel.findOne({
where: { email: allowedUpdates.email },
});
if (emailExists && emailExists.id !== userId) {
throw new BadRequestException("Email is already in use.");
}
}
if (allowedUpdates.password) {
allowedUpdates.password = await bcrypt.hash(allowedUpdates.password, 10);
}
await user.update(updateUserDto);
await user.update(allowedUpdates);
user = await this.userModel.findOne({
where: { id: userId },
attributes: { exclude: ["password"] },
attributes: { exclude: ["password", "refreshToken"] },
});
return { message: "User account updated successfully.", user };
} catch (error) {
console.error(error);
if (error instanceof HttpException) {
throw error;
}
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//get users list
async findAll(): Promise<User[]> {
async findAll(page: number = 1, limit: number = 10) {
try {
return await this.userModel.findAll();
page = Math.max(page, 1);
limit = Math.max(limit, 1);
const offset = (page - 1) * limit;
const users = await this.userModel.findAll({
limit: limit,
offset: offset,
attributes: { exclude: ["refreshToken", "password", "email", "createdAt", "updatedAt", "gender", "phoneNumber"] },
});
return users;
} catch (error) {
if(error instanceof HttpException){
throw error
}
throw new HttpException("An unexpected error occurred while fetching users.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//get users list
async findSpecificUserInfoByUser(userId: number): Promise<User> {
async findSpecificUserInfoByUser(userId: number){
try {
const user = await this.userModel.findByPk(userId);
const user = await this.userModel.findByPk(userId, {
attributes: { exclude: ["refreshToken", "password"] },
});
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
return user;
} catch (error) {
if(error instanceof HttpException){
throw error
}
throw new HttpException("An unexpected error occurred while fetching user information.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//delete a specific user by admin
async deleteUser(userId: number): Promise<{ message: string }> {
async deleteUser(userId: number) {
const user = await this.userModel.findOne({
where: { id: userId },
});
@ -236,6 +294,9 @@ export class UsersService {
return { message: "User deleted successfully." };
} catch (error) {
if(error instanceof HttpException){
throw error
}
throw new HttpException("An error occurred while deleting the user.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}

@ -0,0 +1,97 @@
import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize";
import { AddBalanceResponse } from "./add-balance-response.interface";
import { Transaction } from "./entities/transaction.entity";
import { Wallet } from "./entities/wallet.entity";
@Injectable()
export class WalletService {
constructor(
@InjectModel(Wallet) private walletModel: typeof Wallet,
@InjectModel(Transaction) private transactionModel: typeof Transaction,
) {}
//get wallet info
async getWalletInfo(userId: number) {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) {
const newWallet = await this.walletModel.create({ userId, balance: 0 });
return { walletId: newWallet.id, userId: newWallet.userId, balance: newWallet.balance };
}
return { walletId: wallet.id, userId: wallet.userId, balance: wallet.balance };
}
//get wallet balance
async getBalance(userId: number) {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) {
throw new HttpException("Wallet not found!", HttpStatus.NOT_FOUND);
}
return { balance: wallet.balance };
}
//charge balance of wallet by user
async addBalance(userId: number, amount: number): Promise<AddBalanceResponse> {
try {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (wallet) {
wallet.balance += Number(amount);
await wallet.save();
return { message: "Balance updated successfully.", balance: wallet.balance };
} else {
const newWallet = await this.walletModel.create({ userId, balance: amount });
return { message: "Wallet created and balance added successfully.", balance: newWallet.balance };
}
} catch (error) {
throw new HttpException("An error occurred while adding balance to the wallet.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//process of payment
async processPayment(userId: number, amount: number): Promise<string> {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) {
throw new HttpException("Please Charge your wallet", HttpStatus.NOT_FOUND);
}
if (wallet.balance < amount) {
throw new HttpException("Insufficient funds", HttpStatus.BAD_REQUEST);
}
try {
wallet.balance -= amount;
await this.transactionModel.create({
walletId: wallet.id,
amount: `-${amount}`,
});
await wallet.save();
return "Payment processed successfully";
} catch (error) {
console.error("Error processing payment:", error.message);
throw new HttpException("An error occurred while processing the payment.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//getting transaction
async getTransactionById(userId: number) {
const wallet = await this.getWalletInfo(userId);
if (!wallet) {
throw new HttpException("Wallet not found for the user.", HttpStatus.NOT_FOUND);
}
return await this.transactionModel.findAll({
where: { walletId: wallet.walletId },
});
}
//getting transaction a user (admin)
async getTransactionByIdForAdmin(userId: number) {
const wallet = await this.getWalletInfo(userId);
if (!wallet) {
throw new HttpException("Wallet not found for the user.", HttpStatus.NOT_FOUND);
}
return await this.transactionModel.findAll({
where: { walletId: wallet.walletId },
});
}
}

@ -1,5 +1,5 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request, forwardRef, Inject } from "@nestjs/common";
import { WalletService } from "./wallet.service";
import { WalletService } from "./WalletService";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { PaymentService } from "src/payment/payment.service";
import { RoleGuard } from "src/guard/role.guard";

@ -1,5 +1,5 @@
import { Module } from "@nestjs/common";
import { WalletService } from "./wallet.service";
import { WalletService } from "./WalletService";
import { WalletController } from "./wallet.controller";
import { Wallet } from "./entities/wallet.entity";
import { SequelizeModule } from "@nestjs/sequelize";
@ -11,14 +11,14 @@ import { Transaction } from "./entities/transaction.entity";
@Module({
imports: [
SequelizeModule.forFeature([Wallet,Transaction]),
SequelizeModule.forFeature([Wallet, Transaction]),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "1h" },
}),
],
controllers: [WalletController],
providers: [WalletService, JwtAuthGuard, RoleGuard,PaymentService],
providers: [WalletService, JwtAuthGuard, RoleGuard, PaymentService],
exports: [WalletService],
})
export class WalletModule {}

@ -27,7 +27,7 @@ export class WalletService {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) {
throw new HttpException('Wallet not found!', HttpStatus.NOT_FOUND)
throw new HttpException("Wallet not found!", HttpStatus.NOT_FOUND);
}
return { balance: wallet.balance };
@ -75,7 +75,6 @@ export class WalletService {
throw new HttpException("An error occurred while processing the payment.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//getting transaction
async getTransactionById(userId: number) {
const wallet = await this.getWalletInfo(userId);

Loading…
Cancel
Save