From 50e54e492890e8f773a540e5e2faa3d06164fb51 Mon Sep 17 00:00:00 2001 From: aliMohtarami Date: Sat, 18 Jan 2025 12:54:11 +0330 Subject: [PATCH] Merge admin module with users module --- migrations/20250104074851-create-user.js | 3 +- migrations/20250104094702-create-admin.js | 68 --------- src/admin/admin.controller.ts | 40 ------ src/admin/admin.module.ts | 27 ---- src/admin/admin.service.ts | 163 ---------------------- src/admin/dto/create-Admin.dto.ts | 29 ---- src/admin/dto/login-Admin.dto.ts | 15 -- src/admin/dto/update-user.dto.ts | 33 ----- src/admin/entities/admin.entity.ts | 40 ------ src/app.controller.ts | 4 +- src/app.module.ts | 2 - src/cart/cart.service.ts | 37 +++-- src/payment/payment.controller.ts | 14 +- src/payment/payment.service.ts | 26 ++-- src/users/entities/user.entity.ts | 5 +- src/users/users.controller.ts | 14 +- src/users/users.service.ts | 87 +++++++----- src/wallet/WalletService.ts | 97 +++++++++++++ src/wallet/wallet.controller.ts | 2 +- src/wallet/wallet.module.ts | 6 +- src/wallet/wallet.service.ts | 3 +- 21 files changed, 207 insertions(+), 508 deletions(-) delete mode 100644 migrations/20250104094702-create-admin.js delete mode 100644 src/admin/admin.controller.ts delete mode 100644 src/admin/admin.module.ts delete mode 100644 src/admin/admin.service.ts delete mode 100644 src/admin/dto/create-Admin.dto.ts delete mode 100644 src/admin/dto/login-Admin.dto.ts delete mode 100644 src/admin/dto/update-user.dto.ts delete mode 100644 src/admin/entities/admin.entity.ts create mode 100644 src/wallet/WalletService.ts diff --git a/migrations/20250104074851-create-user.js b/migrations/20250104074851-create-user.js index 74f9dec..51a4a24 100644 --- a/migrations/20250104074851-create-user.js +++ b/migrations/20250104074851-create-user.js @@ -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, diff --git a/migrations/20250104094702-create-admin.js b/migrations/20250104094702-create-admin.js deleted file mode 100644 index 0c55c0b..0000000 --- a/migrations/20250104094702-create-admin.js +++ /dev/null @@ -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'); - }, -}; diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts deleted file mode 100644 index 8e0c448..0000000 --- a/src/admin/admin.controller.ts +++ /dev/null @@ -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); - } -} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts deleted file mode 100644 index 9b9ac62..0000000 --- a/src/admin/admin.module.ts +++ /dev/null @@ -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('JWT_SECRET'), - signOptions: { - expiresIn: config.get('JWT_EXPIRES', '1h'), - }, - }; - }, - }), -], - controllers: [AdminController], - providers: [AdminService], -}) -export class AdminModule {} diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts deleted file mode 100644 index 5bab251..0000000 --- a/src/admin/admin.service.ts +++ /dev/null @@ -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("JWT_SECRET"), - expiresIn: this.configService.get("JWT_EXPIRES") || "1h", - }, - ); - - const refreshToken = this.jwtService.sign( - { id: admin.id, role: admin.role }, - { - secret: this.configService.get("JWT_REFRESH_SECRET"), - expiresIn: this.configService.get("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("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("JWT_SECRET"), - expiresIn: this.configService.get("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}`); - } - } -} diff --git a/src/admin/dto/create-Admin.dto.ts b/src/admin/dto/create-Admin.dto.ts deleted file mode 100644 index 38e4120..0000000 --- a/src/admin/dto/create-Admin.dto.ts +++ /dev/null @@ -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; -} diff --git a/src/admin/dto/login-Admin.dto.ts b/src/admin/dto/login-Admin.dto.ts deleted file mode 100644 index f444f9a..0000000 --- a/src/admin/dto/login-Admin.dto.ts +++ /dev/null @@ -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; - -} diff --git a/src/admin/dto/update-user.dto.ts b/src/admin/dto/update-user.dto.ts deleted file mode 100644 index 3fbabfd..0000000 --- a/src/admin/dto/update-user.dto.ts +++ /dev/null @@ -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; -} diff --git a/src/admin/entities/admin.entity.ts b/src/admin/entities/admin.entity.ts deleted file mode 100644 index 20c308e..0000000 --- a/src/admin/entities/admin.entity.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Column, Table, Model, DataType } from "sequelize-typescript"; - -@Table -export class Admin extends Model { - @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", -} diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..1e7e628 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -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 { diff --git a/src/app.module.ts b/src/app.module.ts index e8e629c..b681570 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -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, diff --git a/src/cart/cart.service.ts b/src/cart/cart.service.ts index 1bcf254..ab5310e 100644 --- a/src/cart/cart.service.ts +++ b/src/cart/cart.service.ts @@ -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 { 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 { diff --git a/src/payment/payment.controller.ts b/src/payment/payment.controller.ts index 2b7c7d0..2275681 100644 --- a/src/payment/payment.controller.ts +++ b/src/payment/payment.controller.ts @@ -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}`); } } - } diff --git a/src/payment/payment.service.ts b/src/payment/payment.service.ts index 74fd4a9..0700e33 100644 --- a/src/payment/payment.service.ts +++ b/src/payment/payment.service.ts @@ -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 { 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 { } } - } diff --git a/src/users/entities/user.entity.ts b/src/users/entities/user.entity.ts index 47af507..49a60d0 100644 --- a/src/users/entities/user.entity.ts +++ b/src/users/entities/user.entity.ts @@ -8,7 +8,10 @@ export class User extends Model { @Column password: string; - @Column({ defaultValue: "user" }) + @Column({ + type: DataType.ENUM("admin", "user"), + allowNull: false, + }) role: string; @Column diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 5e86c86..56da215 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -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"; @@ -51,13 +51,17 @@ export class UsersController { /////////////////////////////////////admin access endpoints///////////////////////////////////////////////////////// //get users list (admin) @UseGuards(RoleGuard) - @Get("users") - async findAll(): Promise { - 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") + @Get("userinfo/:id") async findSpecificUserInfoByUser(@Param("id") id) { return this.usersService.findSpecificUserInfoByUser(id); } diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 2525b72..d9def3f 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -8,6 +8,7 @@ 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 { @@ -18,36 +19,36 @@ export class UsersService { ) {} // Register method - async register(createUserDto: CreateUserDto){ + 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 }, { @@ -55,12 +56,9 @@ export class UsersService { expiresIn: this.configService.get("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) { @@ -70,10 +68,10 @@ export class UsersService { } } // Login method - async login(loginUserDto: LoginUserDto){ + 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) { @@ -115,7 +113,7 @@ export class UsersService { } } // getting access token - async newAccessToken(refreshToken: string){ + async newAccessToken(refreshToken: string) { if (!refreshToken) { throw new HttpException("Refresh token is required.", HttpStatus.BAD_REQUEST); } @@ -147,7 +145,7 @@ export class UsersService { return { accessToken }; } //logout (delete refresh token from database) - async logout(userId: number){ + async logout(userId: number) { if (!userId) { throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST); } @@ -166,7 +164,7 @@ export class UsersService { } } //get information user method - async getProfile(userId: number){ + async getProfile(userId: number) { try { const user = await this.userModel.findOne({ where: { id: userId }, @@ -177,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); } } @@ -187,9 +188,9 @@ export class UsersService { if (!user) { throw new NotFoundException("User not found."); } - + const { refreshToken, ...allowedUpdates } = updateUserDto; - + if (allowedUpdates.username) { const usernameExists = await this.userModel.findOne({ where: { username: allowedUpdates.username }, @@ -198,7 +199,7 @@ export class UsersService { throw new BadRequestException("Username is already in use."); } } - + if (allowedUpdates.phoneNumber) { const phoneNumberExists = await this.userModel.findOne({ where: { phoneNumber: allowedUpdates.phoneNumber }, @@ -207,7 +208,7 @@ export class UsersService { throw new BadRequestException("Phone number is already in use."); } } - + if (allowedUpdates.email) { const emailExists = await this.userModel.findOne({ where: { email: allowedUpdates.email }, @@ -216,49 +217,68 @@ export class UsersService { throw new BadRequestException("Email is already in use."); } } - + if (allowedUpdates.password) { allowedUpdates.password = await bcrypt.hash(allowedUpdates.password, 10); } - + await user.update(allowedUpdates); - + user = await this.userModel.findOne({ where: { id: userId }, attributes: { exclude: ["password", "refreshToken"] }, }); - + return { message: "User account updated successfully.", user }; } catch (error) { - console.error(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 { + 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 { + 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 }, }); @@ -274,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); } } diff --git a/src/wallet/WalletService.ts b/src/wallet/WalletService.ts new file mode 100644 index 0000000..0cde7ec --- /dev/null +++ b/src/wallet/WalletService.ts @@ -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 { + 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 { + 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 }, + }); + } +} diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index 2406378..72a4ab4 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -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"; diff --git a/src/wallet/wallet.module.ts b/src/wallet/wallet.module.ts index a0d2b95..07fe12d 100644 --- a/src/wallet/wallet.module.ts +++ b/src/wallet/wallet.module.ts @@ -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 {} diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 783c5d8..35c0fb1 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -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);