parent
132dfacd5a
commit
50e54e4928
21 changed files with 207 additions and 508 deletions
@ -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", |
|
||||||
} |
|
@ -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 }, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue