Compare commits
No commits in common. "a438cc5f8304ba2ef9994f054ce15088f581900f" and "35919b91f77b5c9f2acf89b3eea1ae5e3a62f559" have entirely different histories.
a438cc5f83
...
35919b91f7
|
@ -96,9 +96,7 @@ import {
|
||||||
import { ItemQueueModule } from './modules/item-related/item-queue/item-queue.module';
|
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 { ItemQueueModel } from './modules/item-related/item-queue/data/models/item-queue.model';
|
||||||
import { QueueBucketModel } from './modules/queue/data/models/queue-bucket.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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ApmModule.register(),
|
ApmModule.register(),
|
||||||
|
@ -159,9 +157,6 @@ import { BookingOrderModule } from './modules/booking-online/order/order.module'
|
||||||
QueueItemModel,
|
QueueItemModel,
|
||||||
QueueModel,
|
QueueModel,
|
||||||
QueueBucketModel,
|
QueueBucketModel,
|
||||||
|
|
||||||
// Booking Online
|
|
||||||
VerificationModel,
|
|
||||||
],
|
],
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
}),
|
}),
|
||||||
|
@ -223,9 +218,6 @@ import { BookingOrderModule } from './modules/booking-online/order/order.module'
|
||||||
GateScanModule,
|
GateScanModule,
|
||||||
|
|
||||||
QueueModule,
|
QueueModule,
|
||||||
|
|
||||||
BookingOnlineAuthModule,
|
|
||||||
BookingOrderModule,
|
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -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"`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
export interface BookingVerification {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
phone_number: string;
|
|
||||||
code?: string;
|
|
||||||
tried?: number;
|
|
||||||
created_at?: number;
|
|
||||||
updated_at?: number;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {}
|
|
|
@ -98,10 +98,6 @@ export class IndexItemManager extends BaseIndexManager<ItemEntity> {
|
||||||
queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`);
|
queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.filterParam.show_to_booking) {
|
|
||||||
queryBuilder.andWhere(`${this.tableName}.show_to_booking = true`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryBuilder;
|
return queryBuilder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,4 @@ export class FilterItemDto extends BaseFilterDto implements FilterItemEntity {
|
||||||
})
|
})
|
||||||
@Transform((body) => body.value == 'true')
|
@Transform((body) => body.value == 'true')
|
||||||
all_item: boolean;
|
all_item: boolean;
|
||||||
|
|
||||||
show_to_booking: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue