Compare commits
2 Commits
61ac8246ab
...
50e54e4928
Author | SHA1 | Date |
---|---|---|
|
50e54e4928 | 1 month ago |
|
132dfacd5a | 1 month ago |
23 changed files with 257 additions and 505 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