Compare commits

...

2 Commits

Author SHA1 Message Date
08c612287a update 2025-09-12 15:27:41 +05:30
861c3cc8c4 update 2025-09-12 15:20:32 +05:30
14 changed files with 179 additions and 333 deletions

View File

@@ -6,7 +6,8 @@
"Bash(npm run migration:generate:*)",
"Bash(npm run migration:run:*)",
"Bash(npm run build:*)",
"Bash(npx typeorm migration:generate:*)"
"Bash(npx typeorm migration:generate:*)",
"Bash(mkdir:*)"
],
"deny": []
}

View File

@@ -20,6 +20,7 @@ import { ConfigurationsModule } from './configurations/configurations.module';
import { PosterAdModule } from './poster-ad/poster-ad.module';
import { VideoAdModule } from './video-ad/video-ad.module';
import { AdPositionModule } from './ad-position/ad-position.module';
import { SmsService } from './sms.service';
@Module({
imports: [
@@ -64,6 +65,7 @@ import { AdPositionModule } from './ad-position/ad-position.module';
AdPositionModule,
],
controllers: [AppController],
providers: [AppService],
providers: [AppService, SmsService],
exports:[SmsService]
})
export class AppModule {}

View File

@@ -8,15 +8,14 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './guard/jwt.guard';
import { RolesGuard } from './guard/role.guard';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Otp } from './entities/otp.entity';
import { SmsService } from './sms.service';
import { OtpModule } from 'src/otp/otp.module';
import { SmsService } from 'src/sms.service';
@Module({
imports: [
UserModule,
PassportModule,
TypeOrmModule.forFeature([Otp]),
OtpModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
@@ -28,8 +27,8 @@ import { SmsService } from './sms.service';
],
controllers: [AuthController],
providers: [
AuthService,
SmsService,
AuthService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,

View File

@@ -11,16 +11,15 @@ import { ConfigService } from '@nestjs/config';
import { User } from 'src/user/entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan } from 'typeorm';
import { Otp } from './entities/otp.entity';
import { SmsService } from './sms.service';
import { SmsService } from '../sms.service';
import { OtpService } from 'src/otp/otp.service';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
@InjectRepository(Otp)
private readonly otpRepository: Repository<Otp>,
private readonly otpService: OtpService,
private readonly smsService: SmsService,
) {}
@@ -78,26 +77,14 @@ export class AuthService {
// }
// Deactivate any existing active LOGIN OTPs for this phone number
await this.otpRepository.update(
{ phone_number: phone, purpose: 'LOGIN', isActive: true },
{ isActive: false },
);
await this.otpService.invalidateOtps(phone, 'LOGIN')
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date();
expiresAt.setMinutes(expiresAt.getMinutes() + 5);
const otp = this.otpRepository.create({
phone_number: phone,
otp_code: otpCode,
expires_at: expiresAt,
is_verified: false,
isActive: true,
purpose: 'LOGIN',
user: user ?? undefined, // Link to user if exists
});
const otp = this.otpService.generateOtp(phone, user??undefined)
await this.otpRepository.save(otp);
// Send SMS using the SMS service
const smsSent = await this.smsService.sendOtp(phone, otpCode);
@@ -115,26 +102,13 @@ export class AuthService {
phone: string,
otpCode: string,
): Promise<{ message: string; token?: string }> {
const otp = await this.otpRepository.findOne({
where: {
phone_number: phone,
otp_code: otpCode,
purpose: 'LOGIN',
isActive: true,
is_verified: false,
expires_at: MoreThan(new Date()),
},
relations: ['user'],
});
const otp = await this.otpService.findValidOtp(phone, "LOGIN")
if (!otp) {
throw new BadRequestException('Invalid or expired OTP');
}
await this.otpRepository.update(otp.id, {
is_verified: true,
isActive: false,
});
await this.otpService.invalidateOtps(phone, "LOGIN")
// If linked to a real user, log them in with their actual data
// Otherwise, create a viewer session
@@ -168,30 +142,14 @@ export class AuthService {
}
// Deactivate any existing verification OTPs
await this.otpRepository.update(
{ phone_number: phone, purpose: 'PHONE_VERIFICATION', isActive: true },
{ isActive: false },
);
await this.otpService.invalidateOtps(phone, 'PHONE_VERIFICATION');
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date();
expiresAt.setMinutes(expiresAt.getMinutes() + 10);
const otp = this.otpRepository.create({
phone_number: phone,
otp_code: otpCode,
expires_at: expiresAt,
is_verified: false,
isActive: true,
purpose: 'PHONE_VERIFICATION',
user,
});
const otp = await this.otpService.generateOtp(phone, user, 'PHONE_VERIFICATION')
await this.otpRepository.save(otp);
const smsSent = await this.smsService.sendOtp(phone, otpCode);
const smsSent = await this.smsService.sendOtp(phone, otp);
if (!smsSent) {
console.log(`SMS failed, Verification OTP for ${phone}: ${otpCode}`);
console.log(`SMS failed, Verification OTP for ${phone}: ${otp}`);
return {
message: 'Verification OTP generated but SMS delivery may have failed',
};
@@ -204,27 +162,14 @@ export class AuthService {
phone: string,
otpCode: string,
): Promise<{ message: string; user?: any; token?: string }> {
const otp = await this.otpRepository.findOne({
where: {
phone_number: phone,
otp_code: otpCode,
purpose: 'PHONE_VERIFICATION',
isActive: true,
is_verified: false,
expires_at: MoreThan(new Date()),
},
relations: ['user'],
});
const otp = await this.otpService.findValidOtp(phone, 'PHONE_VERIFICATION')
if (!otp || !otp.user) {
throw new BadRequestException('Invalid or expired verification OTP');
}
// Mark OTP as used
await this.otpRepository.update(otp.id, {
is_verified: true,
isActive: false,
});
await this.otpService.invalidateOtps(phone, 'PHONE_VERIFICATION');
// Update user verification status
await this.userService.updateUser(otp.user.id, { phone_verified: true });

View File

@@ -37,6 +37,8 @@ export default class UserSeeder implements Seeder {
name: 'Super Admin',
email: 'superadmin@paisaads.com',
phone_number: '9000000001',
email_verified: true,
phone_verified: true,
password: await bcrypt.hash('superadmin123', 10),
role: Role.SUPER_ADMIN,
isActive: true,
@@ -49,6 +51,8 @@ export default class UserSeeder implements Seeder {
name: 'Admin User',
email: 'admin@paisaads.com',
phone_number: '9000000002',
email_verified: true,
phone_verified: true,
password: await bcrypt.hash('admin123', 10),
role: Role.SUPER_ADMIN,
isActive: true,
@@ -61,6 +65,8 @@ export default class UserSeeder implements Seeder {
name: 'Editor User',
email: 'editor@paisaads.com',
phone_number: '9000000003',
email_verified: true,
phone_verified: true,
password: await bcrypt.hash('editor123', 10),
role: Role.EDITOR,
isActive: true,
@@ -73,6 +79,8 @@ export default class UserSeeder implements Seeder {
name: 'Reviewer User',
email: 'reviewer@paisaads.com',
phone_number: '9000000004',
email_verified: true,
phone_verified: true,
password: await bcrypt.hash('reviewer123', 10),
role: Role.REVIEWER,
isActive: true,
@@ -85,6 +93,8 @@ export default class UserSeeder implements Seeder {
name: 'Regular User',
email: 'user@paisaads.com',
phone_number: '9000000005',
email_verified: true,
phone_verified: true,
password: await bcrypt.hash('user123', 10),
role: Role.USER,
isActive: true,

11
src/otp/otp.module.ts Normal file
View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { OtpService } from './otp.service';
import { Otp } from './entities/otp.entity';
@Module({
imports: [TypeOrmModule.forFeature([Otp])],
providers: [OtpService],
exports: [OtpService],
})
export class OtpModule {}

86
src/otp/otp.service.ts Normal file
View File

@@ -0,0 +1,86 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Otp } from './entities/otp.entity';
import { User } from 'src/user/entities/user.entity';
@Injectable()
export class OtpService {
constructor(
@InjectRepository(Otp)
private otpRepository: Repository<Otp>,
) {}
async generateOtp(phoneNumber: string, user?: User, purpose: string = 'LOGIN'): Promise<string> {
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
await this.otpRepository.update(
{ phone_number: phoneNumber, is_verified: false },
{ isActive: false }
);
const otp = this.otpRepository.create({
phone_number: phoneNumber,
otp_code: otpCode,
expires_at: expiresAt,
purpose,
user,
});
await this.otpRepository.save(otp);
return otpCode;
}
async verifyOtp(phoneNumber: string, otpCode: string, purpose: string = 'LOGIN'): Promise<Otp | null> {
const otp = await this.otpRepository.findOne({
where: {
phone_number: phoneNumber,
otp_code: otpCode,
purpose,
is_verified: false,
isActive: true,
},
relations: ['user'],
});
if (!otp) {
return null;
}
if (new Date() > otp.expires_at) {
return null;
}
otp.is_verified = true;
await this.otpRepository.save(otp);
return otp;
}
async invalidateOtps(phoneNumber: string, purpose?: string): Promise<void> {
const whereCondition: any = {
phone_number: phoneNumber,
is_verified: false,
isActive: true,
};
if (purpose) {
whereCondition.purpose = purpose;
}
await this.otpRepository.update(whereCondition, { isActive: false });
}
async findValidOtp(phoneNumber: string, purpose: string = 'LOGIN'): Promise<Otp | null> {
return this.otpRepository.findOne({
where: {
phone_number: phoneNumber,
purpose,
is_verified: false,
isActive: true,
},
relations: ['user'],
});
}
}

View File

@@ -1,112 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from '../app.module';
import { AdStatus } from '../common/enums/ad-status.enum';
import { AdType } from '../common/enums/ad-type';
import { PosterAdService } from '../poster-ad/poster-ad.service';
import { LineAdService } from '../line-ad/line-ad.service';
import { VideoAdService } from '../video-ad/video-ad.service';
import { AdCommentService } from '../ad-comments/ad-comments.service';
import { UserService } from '../user/user.service';
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
try {
const posterAdService = app.get(PosterAdService);
const lineAdService = app.get(LineAdService);
const videoAdService = app.get(VideoAdService);
const adCommentService = app.get(AdCommentService);
const userService = app.get(UserService);
// Get reviewer user
const editor = await userService.findByPhoneOrEmailWithPassword(
'editor@paisaads.com',
);
if (!editor) {
throw new Error('Reviewer user not found');
}
// Get all ads in YET_TO_BE_PUBLISHED status
const posterAds = await posterAdService.findAllByStatuses([
AdStatus.YET_TO_BE_PUBLISHED,
]);
const lineAds = await lineAdService.findAllByStatuses([
AdStatus.YET_TO_BE_PUBLISHED,
]);
const videoAds = await videoAdService.findAllByStatuses([
AdStatus.YET_TO_BE_PUBLISHED,
]);
console.log(`Found ${posterAds.length} poster ads to approve`);
console.log(`Found ${lineAds.length} line ads to approve`);
console.log(`Found ${videoAds.length} video ads to approve`);
// Approve poster ads
for (const ad of posterAds) {
await posterAdService.updateAdStatus(ad.id, AdStatus.YET_TO_BE_PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.PUBLISHED,
comment: 'Bulk approved by editor',
adType: AdType.POSTER,
posterAdId: ad.id,
},
editor.id,
AdType.POSTER,
);
} catch (error) {
console.error(`Error approving poster ad: ${ad.id}`, error);
}
console.log(`Approved poster ad: ${ad.id}`);
}
// Approve line ads
for (const ad of lineAds) {
await lineAdService.updateAdStatus(ad.id, AdStatus.PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.PUBLISHED,
comment: 'Bulk approved by editor',
adType: AdType.LINE,
lineAdId: ad.id,
},
editor.id,
AdType.LINE,
);
} catch (error) {
console.error(`Error approving line ad: ${ad.id}`, error);
}
console.log(`Approved line ad: ${ad.id}`);
}
// Approve video ads
for (const ad of videoAds) {
await videoAdService.updateAdStatus(ad.id, AdStatus.PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.PUBLISHED,
comment: 'Bulk published by editor',
adType: AdType.VIDEO,
videoAdId: ad.id,
},
editor.id,
AdType.VIDEO,
);
} catch (error) {
console.error(`Error approving video ad: ${ad.id}`, error);
}
console.log(`Approved video ad: ${ad.id}`);
}
console.log('All ads have been approved successfully!');
} catch (error) {
console.error('Error approving ads:', error);
} finally {
await app.close();
}
}
bootstrap();

View File

@@ -1,112 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from '../app.module';
import { AdStatus } from '../common/enums/ad-status.enum';
import { AdType } from '../common/enums/ad-type';
import { PosterAdService } from '../poster-ad/poster-ad.service';
import { LineAdService } from '../line-ad/line-ad.service';
import { VideoAdService } from '../video-ad/video-ad.service';
import { AdCommentService } from '../ad-comments/ad-comments.service';
import { UserService } from '../user/user.service';
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
try {
const posterAdService = app.get(PosterAdService);
const lineAdService = app.get(LineAdService);
const videoAdService = app.get(VideoAdService);
const adCommentService = app.get(AdCommentService);
const userService = app.get(UserService);
// Get reviewer user
const reviewer = await userService.findByPhoneOrEmailWithPassword(
'reviewer@paisaads.com',
);
if (!reviewer) {
throw new Error('Reviewer user not found');
}
// Get all ads in FOR_REVIEW status
const posterAds = await posterAdService.findAllByStatuses([
AdStatus.FOR_REVIEW,
]);
const lineAds = await lineAdService.findAllByStatuses([
AdStatus.FOR_REVIEW,
]);
const videoAds = await videoAdService.findAllByStatuses([
AdStatus.FOR_REVIEW,
]);
console.log(`Found ${posterAds.length} poster ads to approve`);
console.log(`Found ${lineAds.length} line ads to approve`);
console.log(`Found ${videoAds.length} video ads to approve`);
// Approve poster ads
for (const ad of posterAds) {
await posterAdService.updateAdStatus(ad.id, AdStatus.YET_TO_BE_PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.YET_TO_BE_PUBLISHED,
comment: 'Bulk approved by reviewer',
adType: AdType.POSTER,
posterAdId: ad.id,
},
reviewer.id,
AdType.POSTER,
);
} catch (error) {
console.error(`Error approving poster ad: ${ad.id}`, error);
}
console.log(`Approved poster ad: ${ad.id}`);
}
// Approve line ads
for (const ad of lineAds) {
await lineAdService.updateAdStatus(ad.id, AdStatus.YET_TO_BE_PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.YET_TO_BE_PUBLISHED,
comment: 'Bulk approved by reviewer',
adType: AdType.LINE,
lineAdId: ad.id,
},
reviewer.id,
AdType.LINE,
);
} catch (error) {
console.error(`Error approving line ad: ${ad.id}`, error);
}
console.log(`Approved line ad: ${ad.id}`);
}
// Approve video ads
for (const ad of videoAds) {
await videoAdService.updateAdStatus(ad.id, AdStatus.YET_TO_BE_PUBLISHED);
try {
await adCommentService.create(
{
actionType: AdStatus.YET_TO_BE_PUBLISHED,
comment: 'Bulk approved by reviewer',
adType: AdType.VIDEO,
videoAdId: ad.id,
},
reviewer.id,
AdType.VIDEO,
);
} catch (error) {
console.error(`Error approving video ad: ${ad.id}`, error);
}
console.log(`Approved video ad: ${ad.id}`);
}
console.log('All ads have been approved successfully!');
} catch (error) {
console.error('Error approving ads:', error);
} finally {
await app.close();
}
}
bootstrap();

View File

@@ -3,7 +3,7 @@ import { BaseEntity } from 'src/common/types/base-entity';
import { Column, Entity, JoinColumn, OneToOne, OneToMany } from 'typeorm';
import { Admin } from './admin.entity';
import { Customer } from './customer.entity';
import { Otp } from 'src/auth/entities/otp.entity';
import { Otp } from 'src/otp/entities/otp.entity';
@Entity('users')
export class User extends BaseEntity {

View File

@@ -6,11 +6,14 @@ import { User } from './entities/user.entity';
import { Admin } from './entities/admin.entity';
import { Customer } from './entities/customer.entity';
import { ImageModule } from 'src/image/image.module';
import { SmsService } from 'src/sms.service';
import { OtpService } from 'src/otp/otp.service';
import { OtpModule } from 'src/otp/otp.module';
@Module({
imports: [TypeOrmModule.forFeature([User, Admin, Customer]), ImageModule],
imports: [TypeOrmModule.forFeature([User, Admin, Customer]), ImageModule,OtpModule],
controllers: [UserController],
providers: [UserService],
providers: [UserService,SmsService],
exports: [UserService],
})
export class UserModule {}

View File

@@ -18,6 +18,8 @@ import * as bcrypt from 'bcrypt';
import { Role } from 'src/common/enums/role.enum';
import { CreateUserAndCustomerDto } from './dto/create-user-and-customer';
import { UpdateCustomerDto } from './dto/update-customer';
import { SmsService } from 'src/sms.service';
import { OtpService } from 'src/otp/otp.service';
@Injectable()
export class UserService {
@@ -26,6 +28,8 @@ export class UserService {
@InjectRepository(Customer) private customerRepo: Repository<Customer>,
@InjectRepository(Admin) private adminRepo: Repository<Admin>,
private imageService: ImageService,
private smsService: SmsService,
private otpService: OtpService,
) {}
// Utility hashing
@@ -82,39 +86,48 @@ export class UserService {
// throw new BadRequestException('Proof image does not exist');
// }
await this.userRepo.manager.transaction(async (manager) => {
// Create user
const hashed = await this.hashPassword(dto.password);
const user = manager.create(User, {
name: dto.name,
email: dto.email,
phone_number: dto.phone_number,
secondary_number: dto.secondary_number,
password: hashed,
role: Role.USER,
isActive: true,
});
console.log('user', user);
const savedUser = await manager.save(User, user);
let proof;
if (dto.proof) {
proof = await this.imageService.confirmImage(dto.proof);
}
try {
await this.userRepo.manager.transaction(async (manager) => {
// Create user
const hashed = await this.hashPassword(dto.password);
const user = manager.create(User, {
name: dto.name,
email: dto.email,
phone_number: dto.phone_number,
secondary_number: dto.secondary_number,
password: hashed,
role: Role.USER,
isActive: true,
});
const savedUser = await manager.save(User, user);
let proof;
if (dto.proof) {
proof = await this.imageService.confirmImage(dto.proof);
}
// Create customer and link image
const customer = manager.create(Customer, {
country: dto.country,
country_id: dto.country_id,
state: dto.state,
state_id: dto.state_id,
city: dto.city,
city_id: dto.city_id,
gender: dto.gender,
user: savedUser,
proof: proof ?? undefined,
// Create customer and link image
const customer = manager.create(Customer, {
country: dto.country,
country_id: dto.country_id,
state: dto.state,
state_id: dto.state_id,
city: dto.city,
city_id: dto.city_id,
gender: dto.gender,
user: savedUser,
proof: proof ?? undefined,
});
await manager.save(Customer, customer);
const otp = await this.otpService.generateOtp(
user.phone_number,
user,
'LOGIN',
);
await this.smsService.sendOtp(user.phone_number, otp);
});
await manager.save(Customer, customer);
});
} catch (exception) {
throw new BadRequestException(exception);
}
}
// -- Getters