Compare commits

..

No commits in common. "a438cc5f8304ba2ef9994f054ce15088f581900f" and "35919b91f77b5c9f2acf89b3eea1ae5e3a62f559" have entirely different histories.

12 changed files with 1 additions and 351 deletions

View File

@ -96,9 +96,7 @@ import {
import { ItemQueueModule } from './modules/item-related/item-queue/item-queue.module';
import { ItemQueueModel } from './modules/item-related/item-queue/data/models/item-queue.model';
import { QueueBucketModel } from './modules/queue/data/models/queue-bucket.model';
import { VerificationModel } from './modules/booking-online/authentication/data/models/verification.model';
import { BookingOnlineAuthModule } from './modules/booking-online/authentication/auth.module';
import { BookingOrderModule } from './modules/booking-online/order/order.module';
@Module({
imports: [
ApmModule.register(),
@ -159,9 +157,6 @@ import { BookingOrderModule } from './modules/booking-online/order/order.module'
QueueItemModel,
QueueModel,
QueueBucketModel,
// Booking Online
VerificationModel,
],
synchronize: false,
}),
@ -223,9 +218,6 @@ import { BookingOrderModule } from './modules/booking-online/order/order.module'
GateScanModule,
QueueModule,
BookingOnlineAuthModule,
BookingOrderModule,
],
controllers: [],
providers: [

View File

@ -1,15 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class BookingAuthentication1748313715598 implements MigrationInterface {
name = 'BookingAuthentication1748313715598';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "booking_verification" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "phone_number" character varying NOT NULL, "code" character varying, "tried" integer NOT NULL DEFAULT '0', "created_at" bigint NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) * 1000, "updated_at" bigint NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) * 1000, CONSTRAINT "PK_046e9288c7dd05c7259275d9fc0" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "booking_verification"`);
}
}

View File

@ -1,25 +0,0 @@
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { VerificationModel } from './data/models/verification.model';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingAuthenticationController } from './infrastructure/controllers/booking-authentication.controller';
import { VerificationService } from './data/services/verification.service';
import { JwtModule } from '@nestjs/jwt';
import { JWT_EXPIRED } from 'src/core/sessions/constants';
import { JWT_SECRET } from 'src/core/sessions/constants';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([VerificationModel], CONNECTION_NAME.DEFAULT),
JwtModule.register({
secret: JWT_SECRET,
signOptions: { expiresIn: JWT_EXPIRED },
}),
],
controllers: [BookingAuthenticationController],
providers: [VerificationService],
})
export class BookingOnlineAuthModule {}

View File

@ -1,29 +0,0 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { BookingVerification } from '../../domain/entities/booking-verification.entity';
@Entity('booking_verification')
export class VerificationModel implements BookingVerification {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
phone_number: string;
@Column({ nullable: true })
code?: string;
@Column({ default: 0 })
tried: number;
@Column({ type: 'bigint', default: () => 'EXTRACT(EPOCH FROM NOW()) * 1000' })
created_at: number;
@Column({
type: 'bigint',
default: () => 'EXTRACT(EPOCH FROM NOW()) * 1000',
onUpdate: 'EXTRACT(EPOCH FROM NOW()) * 1000',
})
updated_at: number;
}

View File

@ -1,95 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { VerificationModel } from '../models/verification.model';
import { BookingVerification } from '../../domain/entities/booking-verification.entity';
import { UnprocessableEntityException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
export class VerificationService {
constructor(
@InjectRepository(VerificationModel)
private readonly verificationRepository: Repository<VerificationModel>,
private readonly jwtService: JwtService,
) {}
maxAttempts = 3;
expiredTime = 5 * 60 * 1000;
expiredTimeRegister = 1 * 60 * 1000;
async generateToken(payload: BookingVerification) {
return this.jwtService.sign({
phone_number: payload.phone_number,
name: payload.name,
created_at: payload.created_at,
});
}
async register(data: BookingVerification) {
const currentTime = Math.floor(Date.now()); // current time in seconds
if (
data.created_at &&
currentTime - data.created_at > this.expiredTimeRegister
) {
throw new UnprocessableEntityException('Please try again in 1 minute');
}
// Generate a 4 digit OTP code
data.code = Math.floor(1000 + Math.random() * 9000).toString();
data.tried = 0;
data.updated_at = currentTime;
let verification = await this.verificationRepository.findOne({
where: { phone_number: data.phone_number },
});
if (verification) {
// Update existing record
verification = this.verificationRepository.merge(verification, data);
} else {
// Create new record
verification = this.verificationRepository.create(data);
}
return this.verificationRepository.save(verification);
}
async findByPhoneNumber(phoneNumber: string) {
return this.verificationRepository.findOne({
where: { phone_number: phoneNumber },
});
}
async verify(data: BookingVerification): Promise<BookingVerification> {
const verification = await this.findByPhoneNumber(data.phone_number);
if (!verification) {
throw new UnprocessableEntityException('Phone number not found');
}
if (verification.tried >= this.maxAttempts) {
throw new UnprocessableEntityException(
'Too many attempts, please resend OTP Code',
);
}
if (verification.code != data.code) {
verification.tried++;
await this.verificationRepository.save(verification);
throw new UnprocessableEntityException('Invalid verification code');
}
const currentTime = Math.floor(Date.now());
if (
verification.updated_at &&
currentTime - verification.updated_at > this.expiredTime
) {
throw new UnprocessableEntityException('Verification code expired');
}
return verification;
}
async update(id: string, data: BookingVerification) {
return this.verificationRepository.update(id, data);
}
async delete(id: string) {
return this.verificationRepository.delete(id);
}
}

View File

@ -1,9 +0,0 @@
export interface BookingVerification {
id: string;
name: string;
phone_number: string;
code?: string;
tried?: number;
created_at?: number;
updated_at?: number;
}

View File

@ -1,43 +0,0 @@
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import {
BookingVerificationDto,
VerificationCodeDto,
} from '../dto/booking-verification.dto';
import { VerificationService } from '../../data/services/verification.service';
import { Public } from 'src/core/guards/domain/decorators/unprotected.guard';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Booking Authentication')
@Public()
@Controller('booking-authentication')
export class BookingAuthenticationController {
constructor(
private readonly bookingAuthenticationService: VerificationService,
) {}
@Post('verify')
async verify(@Body() body: VerificationCodeDto) {
const verification = await this.bookingAuthenticationService.verify(body);
const token = await this.bookingAuthenticationService.generateToken(
verification,
);
return {
message: `Verification successful for ${verification.phone_number}`,
token,
};
}
@Post('register')
async register(@Body() body: BookingVerificationDto) {
const verification = await this.bookingAuthenticationService.register(body);
return {
message: `Verification code sent to ${verification.phone_number}`,
};
}
@Get('get-by-phone/:phone_number')
async getByPhoneNumber(@Param('phone_number') phone_number: string) {
return this.bookingAuthenticationService.findByPhoneNumber(phone_number);
}
}

View File

@ -1,67 +0,0 @@
import { BookingVerification } from '../../domain/entities/booking-verification.entity';
import { IsString, IsNotEmpty, Matches } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class BookingVerificationDto implements BookingVerification {
id: string;
@ApiProperty({
type: String,
required: true,
example: 'John Doe',
description: 'Name of the person booking',
})
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({
type: String,
required: true,
example: '628123456789',
description: 'Phone number containing only numeric characters',
})
@IsString()
@IsNotEmpty()
@Matches(/^\d+$/, {
message: 'phone_number must contain only numeric characters',
})
phone_number: string;
}
export class VerificationCodeDto implements BookingVerification {
id: string;
@ApiProperty({
type: String,
required: true,
example: 'John Doe',
description: 'Name of the person booking',
})
@IsString()
@IsNotEmpty()
name: string;
@ApiProperty({
type: String,
required: true,
example: '628123456789',
description: 'Phone number containing only numeric characters',
})
@IsString()
@IsNotEmpty()
@Matches(/^\d+$/, {
message: 'phone_number must contain only numeric characters',
})
phone_number: string;
@ApiProperty({
type: String,
required: true,
example: '1234',
description: 'Verification code',
})
@IsString()
@IsNotEmpty()
code?: string;
}

View File

@ -1,35 +0,0 @@
import { Controller, Get, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { Public } from 'src/core/guards';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { ItemReadService } from 'src/modules/item-related/item/data/services/item-read.service';
import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity';
import { IndexItemManager } from 'src/modules/item-related/item/domain/usecases/managers/index-item.manager';
import { FilterItemDto } from 'src/modules/item-related/item/infrastructure/dto/filter-item.dto';
@ApiTags('Booking Item')
@Controller('booking-item')
@Public(true)
export class ItemController {
constructor(
private indexManager: IndexItemManager,
private serviceData: ItemReadService,
) {}
@Get()
async index(
@Query() params: FilterItemDto,
): Promise<PaginationResponse<ItemEntity>> {
try {
params.show_to_booking = true;
this.indexManager.setFilterParam(params);
this.indexManager.setService(this.serviceData, TABLE_NAME.ITEM);
await this.indexManager.execute();
return this.indexManager.getResult();
} catch (error) {
console.log(error);
throw error;
}
}
}

View File

@ -1,18 +0,0 @@
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
import { ItemModule } from 'src/modules/item-related/item/item.module';
import { ItemController } from './infrastructure/item.controller';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([ItemModel], CONNECTION_NAME.DEFAULT),
ItemModule,
],
controllers: [ItemController],
providers: [],
})
export class BookingOrderModule {}

View File

@ -98,10 +98,6 @@ export class IndexItemManager extends BaseIndexManager<ItemEntity> {
queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`);
}
if (this.filterParam.show_to_booking) {
queryBuilder.andWhere(`${this.tableName}.show_to_booking = true`);
}
return queryBuilder;
}
}

View File

@ -40,6 +40,4 @@ export class FilterItemDto extends BaseFilterDto implements FilterItemEntity {
})
@Transform((body) => body.value == 'true')
all_item: boolean;
show_to_booking: boolean;
}