diff --git a/auto-bid-admin/index.html b/auto-bid-admin/index.html
index 8ac7b8f..38abf8d 100644
--- a/auto-bid-admin/index.html
+++ b/auto-bid-admin/index.html
@@ -1,13 +1,13 @@
-
-
-
-
- Vite + React + TS
-
-
-
-
-
+
+
+
+
+ Bid System
+
+
+
+
+
diff --git a/auto-bid-server/src/modules/bids/controllers/client/bids.controller.ts b/auto-bid-server/src/modules/bids/controllers/client/bids.controller.ts
index 3cd5366..0945b48 100644
--- a/auto-bid-server/src/modules/bids/controllers/client/bids.controller.ts
+++ b/auto-bid-server/src/modules/bids/controllers/client/bids.controller.ts
@@ -1,3 +1,4 @@
+import AppResponse from '@/response/app-response';
import {
Body,
Controller,
@@ -12,8 +13,10 @@ import {
import { EventEmitter2 } from '@nestjs/event-emitter';
import { FileInterceptor } from '@nestjs/platform-express';
import { plainToClass } from 'class-transformer';
-import { diskStorage, memoryStorage } from 'multer';
+import { memoryStorage } from 'multer';
+import { ClientInfoUpdateBidDto } from '../../dto/bid/client-info-update-bid.dto';
import { ClientUpdateBidDto } from '../../dto/bid/client-update-bid.dto';
+import { ClientUpdateLoginStatusDto } from '../../dto/bid/client-update-login-status.dto';
import { CreateBidDto } from '../../dto/bid/create-bid.dto';
import { UpdateStatusByPriceDto } from '../../dto/bid/update-status-by-price.dto';
import { Bid } from '../../entities/bid.entity';
@@ -21,11 +24,6 @@ import { WebBid } from '../../entities/wed-bid.entity';
import { BidsService } from '../../services/bids.service';
import { WebBidsService } from '../../services/web-bids.service';
import { Event } from '../../utils/events';
-import AppResponse from '@/response/app-response';
-import { ClientUpdateLoginStatusDto } from '../../dto/bid/client-update-login-status.dto';
-import { UpdateBidDto } from '../../dto/bid/update-bid.dto';
-import { ClientInfoUpdateBidDto } from '../../dto/bid/client-info-update-bid.dto';
-import path from 'path';
@Controller('bids')
export class BidsController {
@@ -100,14 +98,14 @@ export class BidsController {
@Post('test')
async test(@Body('code') code: string) {
const webBid = await this.webBidService.webBidRepo.findOne({
- where: { id: 4 },
- // where: { id: 1 },
+ // where: { id: 4 },
+ where: { id: 1 },
});
this.eventEmitter.emit(Event.verifyCode(webBid), {
code,
- name: 'LAWSONS',
- // name: 'LANGTONS',
+ // name: 'LAWSONS',
+ name: 'LANGTONS',
web_bid: plainToClass(WebBid, webBid),
});
diff --git a/auto-bid-server/src/modules/bids/services/bid-metadatas.service.ts b/auto-bid-server/src/modules/bids/services/bid-metadatas.service.ts
index 045c4a1..6de3ae1 100644
--- a/auto-bid-server/src/modules/bids/services/bid-metadatas.service.ts
+++ b/auto-bid-server/src/modules/bids/services/bid-metadatas.service.ts
@@ -109,6 +109,10 @@ export class BidMetadatasService {
return this.getArrivalOffsetSecondsSandbox(metadatas);
};
+ isSandbox(metadatas: BidMetadata[]) {
+ return this.getMode(metadatas) !== 'live';
+ }
+
async delete(id: Bid['id']) {
const bid = await this.bidMetadataRepo.findOne({ where: { id } });
diff --git a/auto-bid-server/src/modules/bids/services/bids.service.ts b/auto-bid-server/src/modules/bids/services/bids.service.ts
index 662ad68..66af446 100644
--- a/auto-bid-server/src/modules/bids/services/bids.service.ts
+++ b/auto-bid-server/src/modules/bids/services/bids.service.ts
@@ -24,6 +24,7 @@ import { join } from 'path';
import AppResponse from 'src/response/app-response';
import {
extractModelId,
+ isTimePassedByMinutes,
isTimeReached,
parseVideoFileName,
subtractMinutes,
@@ -156,6 +157,36 @@ export class BidsService {
await this.emitAllBidEvent();
+ const warnings = [];
+
+ if (!webBid.username || !webBid.password) {
+ // Add warning message
+ warnings.push(
+ `Account setup for ${webBid.origin_url} website is not yet complete.`,
+ );
+ }
+
+ if (!webBid.active) {
+ // Add warning message
+ warnings.push(
+ `${webBid.origin_url} is disabled. Please enable to continue.`,
+ );
+ }
+
+ if (warnings.length) {
+ // Send event warning
+ this.eventEmitter.emit(Event.SEND_WARNING, {
+ title: `System Warning: Abnormal Data Detected`,
+ messages: warnings,
+ });
+ }
+
+ // Send event success
+ this.eventEmitter.emit(Event.SEND_SUCCESS, {
+ title: 'Successfully Added Product',
+ messages: [`Successfully added product to bid list: ${data.url}`],
+ });
+
return AppResponse.toResponse(
response ? response(result) : plainToClass(Bid, result),
);
@@ -356,6 +387,12 @@ export class BidsService {
close_time,
arrival_offset_seconds / 60,
);
+
+ // update
+ await this.bidsRepo.update(
+ { id },
+ { start_bid_time: bid.start_bid_time },
+ );
}
// Cập nhật thời gian kết thúc đấu giá nếu `close_time` mới lớn hơn `close_time` cũ
@@ -365,6 +402,13 @@ export class BidsService {
) {
bid.close_time = close_time;
bid.close_time_ts = new Date(close_time);
+
+ // update
+ await this.bidsRepo.update(
+ { id },
+ { close_time: bid.close_time, close_time_ts: bid.close_time_ts },
+ );
+
bid = await this.getBidForClientUpdate(id);
}
@@ -384,7 +428,6 @@ export class BidsService {
}
} else {
// Nếu phiên đấu giá vẫn đang diễn ra và giá hiện tại vượt quá giới hạn đặt của người dùng
-
if (
data.current_price > bid.max_price + bid.plus_price ||
(!bid.histories.length &&
@@ -429,6 +472,30 @@ export class BidsService {
this.notificationService.emitBidStatus(result);
}
+ // Send INFO
+ if (bid.current_price < result.current_price && bid.histories.length) {
+ this.eventEmitter.emit(Event.SEND_INFO, {
+ title: 'New Higher Bid Detected',
+ messages: [
+ `Another user just placed a higher bid of ${result.current_price} on ${bid.name}.`,
+ ],
+ });
+ }
+
+ // Send error when bidding fail
+ if (
+ isTimePassedByMinutes(result.start_bid_time, 1) &&
+ !bid.histories.length &&
+ !this.bidMetadatasService.isSandbox(bid.metadata)
+ ) {
+ this.eventEmitter.emit(Event.SEND_ERROR, {
+ title: 'Bidding Error Detected',
+ messages: [
+ `An error occurred while placing a bid on ${bid.name}. Please check the system.`,
+ ],
+ });
+ }
+
// Trả về kết quả cập nhật dưới dạng response chuẩn
return AppResponse.toResponse(plainToClass(Bid, result));
}
diff --git a/auto-bid-server/src/modules/bids/utils/events.ts b/auto-bid-server/src/modules/bids/utils/events.ts
index 07bed77..3158cd6 100644
--- a/auto-bid-server/src/modules/bids/utils/events.ts
+++ b/auto-bid-server/src/modules/bids/utils/events.ts
@@ -10,6 +10,10 @@ export class Event {
public static BID_SUBMITED = 'bid-submited';
public static BID_STATUS = 'bid-status';
public static BID_DEMO = 'bid-demo';
+ public static SEND_WARNING = 'send-warning';
+ public static SEND_SUCCESS = 'send-success';
+ public static SEND_ERROR = 'send-error';
+ public static SEND_INFO = 'send-info';
public static verifyCode(data: WebBid) {
return `${this.VERIFY_CODE}.${data.origin_url}`;
diff --git a/auto-bid-server/src/modules/mails/services/mails.service.ts b/auto-bid-server/src/modules/mails/services/mails.service.ts
index 28a27da..e711e7d 100644
--- a/auto-bid-server/src/modules/mails/services/mails.service.ts
+++ b/auto-bid-server/src/modules/mails/services/mails.service.ts
@@ -468,4 +468,68 @@ export class MailsService {
`;
}
+
+ generateWarningEmailTemplate(title: string, messages: string[]): string {
+ const timestamp = new Date().toLocaleString(); // hoặc new Date().toISOString()
+
+ return `
+
+
⚠️ ${title}
+
+ ${messages.map((msg) => `- ${msg}
`).join('')}
+
+
+ Sent at: ${timestamp}
+
+
+ `;
+ }
+
+ generateSuccessEmailTemplate(title: string, messages: string[]): string {
+ const timestamp = new Date().toLocaleString(); // hoặc new Date().toISOString()
+
+ return `
+
+
✅ ${title}
+
+ ${messages.map((msg) => `- ${msg}
`).join('')}
+
+
+ Sent at: ${timestamp}
+
+
+ `;
+ }
+
+ generateErrorEmailTemplate(title: string, messages: string[]): string {
+ const timestamp = new Date().toLocaleString();
+
+ return `
+
+
❌ ${title}
+
+ ${messages.map((msg) => `- ${msg}
`).join('')}
+
+
+ Sent at: ${timestamp}
+
+
+ `;
+ }
+
+ generateInfoEmailTemplate(title: string, messages: string[]): string {
+ const timestamp = new Date().toLocaleString();
+
+ return `
+
+
ℹ️ ${title}
+
+ ${messages.map((msg) => `- ${msg}
`).join('')}
+
+
+ Sent at: ${timestamp}
+
+
+ `;
+ }
}
diff --git a/auto-bid-server/src/modules/notification/listeners/admin-notification.listener.ts b/auto-bid-server/src/modules/notification/listeners/admin-notification.listener.ts
index 5263f5d..4509201 100644
--- a/auto-bid-server/src/modules/notification/listeners/admin-notification.listener.ts
+++ b/auto-bid-server/src/modules/notification/listeners/admin-notification.listener.ts
@@ -69,4 +69,98 @@ export class AdminNotificationListener {
moment(new Date()).format('YYYY-MM-DD HH:mm'),
});
}
+
+ @OnEvent(Event.SEND_WARNING)
+ async handleSendWarning({
+ title,
+ messages,
+ }: {
+ title: string;
+ messages: string[];
+ }) {
+ const mails =
+ (await this.configsSerice.getConfig('MAIL_SCRAP_REPORT')).value || '';
+
+ const html = this.mailsService.generateWarningEmailTemplate(
+ title,
+ messages,
+ );
+
+ this.mailsService.sendHtmlMailJob({
+ to: mails,
+ html: html,
+ subject:
+ '[WARNING] Auto Auctions System ' +
+ moment(new Date()).format('YYYY-MM-DD HH:mm'),
+ });
+ }
+
+ @OnEvent(Event.SEND_SUCCESS)
+ async handleSendSuccess({
+ title,
+ messages,
+ }: {
+ title: string;
+ messages: string[];
+ }) {
+ const mails =
+ (await this.configsSerice.getConfig('MAIL_SCRAP_REPORT')).value || '';
+
+ const html = this.mailsService.generateSuccessEmailTemplate(
+ title,
+ messages,
+ );
+
+ this.mailsService.sendHtmlMailJob({
+ to: mails,
+ html: html,
+ subject:
+ '[SUCCESS] Auto Auctions System ' +
+ moment(new Date()).format('YYYY-MM-DD HH:mm'),
+ });
+ }
+
+ @OnEvent(Event.SEND_ERROR)
+ async handleSendError({
+ title,
+ messages,
+ }: {
+ title: string;
+ messages: string[];
+ }) {
+ const mails =
+ (await this.configsSerice.getConfig('MAIL_SCRAP_REPORT')).value || '';
+
+ const html = this.mailsService.generateErrorEmailTemplate(title, messages);
+
+ this.mailsService.sendHtmlMailJob({
+ to: mails,
+ html: html,
+ subject:
+ '[ERROR] Auto Auctions System ' +
+ moment(new Date()).format('YYYY-MM-DD HH:mm'),
+ });
+ }
+
+ @OnEvent(Event.SEND_INFO)
+ async handleSendInfo({
+ title,
+ messages,
+ }: {
+ title: string;
+ messages: string[];
+ }) {
+ const mails =
+ (await this.configsSerice.getConfig('MAIL_SCRAP_REPORT')).value || '';
+
+ const html = this.mailsService.generateInfoEmailTemplate(title, messages);
+
+ this.mailsService.sendHtmlMailJob({
+ to: mails,
+ html: html,
+ subject:
+ '[INFO] Auto Auctions System ' +
+ moment(new Date()).format('YYYY-MM-DD HH:mm'),
+ });
+ }
}
diff --git a/auto-bid-server/src/ultils/index.ts b/auto-bid-server/src/ultils/index.ts
index f7d1dba..e7e070a 100644
--- a/auto-bid-server/src/ultils/index.ts
+++ b/auto-bid-server/src/ultils/index.ts
@@ -65,6 +65,19 @@ export function isTimeReached(targetTime: string) {
return now >= targetDate;
}
+export function isTimePassedByMinutes(
+ targetTime: string,
+ minutes: number,
+): boolean {
+ const targetDate = new Date(targetTime);
+ const now = new Date();
+
+ const diffMs = now.getTime() - targetDate.getTime(); // hiệu số milliseconds
+ const diffMinutes = diffMs / (1000 * 60);
+
+ return diffMinutes >= minutes;
+}
+
export function extractDomain(url: string): string | null {
try {
const parsedUrl = new URL(url);
diff --git a/auto-bid-tool/models/allbids.com.au/allbids-api-bid.js b/auto-bid-tool/models/allbids.com.au/allbids-api-bid.js
index 75f7382..c7dca06 100644
--- a/auto-bid-tool/models/allbids.com.au/allbids-api-bid.js
+++ b/auto-bid-tool/models/allbids.com.au/allbids-api-bid.js
@@ -16,12 +16,6 @@ export class AllbidsApiBid extends ApiBid {
const currentUrl = await this.page_context.url();
- console.log({
- filePath,
- currentUrl,
- a: currentUrl.includes(configs.WEB_URLS.ALLBIDS.LOGIN_URL),
- });
-
if (currentUrl.includes(configs.WEB_URLS.ALLBIDS.LOGIN_URL)) return false;
return (
diff --git a/auto-bid-tool/models/allbids.com.au/allbids-product-bid.js b/auto-bid-tool/models/allbids.com.au/allbids-product-bid.js
index fcba512..1fbaaf5 100644
--- a/auto-bid-tool/models/allbids.com.au/allbids-product-bid.js
+++ b/auto-bid-tool/models/allbids.com.au/allbids-product-bid.js
@@ -33,7 +33,11 @@ export class AllbidsProductBid extends ProductBid {
for (let i = 0; i < elements.length; i++) {
try {
const scope = angular.element(elements[i]).scope();
- if (scope?.auction) {
+ if (
+ scope?.auction &&
+ scope?.auction.aucID === this.model &&
+ scope?.auction?.aucBidIncrement
+ ) {
data = scope.auction;
break;
}
@@ -134,7 +138,7 @@ export class AllbidsProductBid extends ProductBid {
const response = await this.page_context.evaluate(
async (aucID, bidAmount, submitUrl) => {
try {
- const url = `${submitUrl}?aucID=${aucID}&bidAmount=${bidAmount}&bidType=maximum`;
+ const url = `${submitUrl}?aucID=${aucID}&bidAmount=${bidAmount}`;
const res = await fetch(url, {
method: "POST",
@@ -153,7 +157,7 @@ export class AllbidsProductBid extends ProductBid {
},
this.model,
this.max_price,
- configs.WEB_URLS.ALLBIDS.PLACE_BID
+ configs.WEB_CONFIGS.ALLBIDS.PLACE_BID
);
return response;
@@ -183,7 +187,7 @@ export class AllbidsProductBid extends ProductBid {
}
},
this.model,
- configs.WEB_URLS.ALLBIDS.PLACE_BID
+ configs.WEB_CONFIGS.ALLBIDS.PLACE_BID
);
return response;
@@ -329,26 +333,32 @@ export class AllbidsProductBid extends ProductBid {
await this.page_context.reload({ waitUntil: "networkidle0" });
- const { aucUserMaxBid } = await this.waitForApiResponse();
- console.log(`📡 [${this.id}] API Response received:`, { aucUserMaxBid });
+ // const { aucUserMaxBid } = await this.waitForApiResponse();
+ console.log(`📡 [${this.id}] API Response received:`, {
+ aucUserMaxBid: this.max_price,
+ });
// 📌 Kiểm tra trạng thái đấu giá từ API
- if (aucUserMaxBid == this.max_price) {
+ if (
+ data?.bidResult?.result ||
+ data?.bidResult?.bidAmount == this.max_price
+ ) {
console.log(`📸 [${this.id}] Taking bid success snapshot...`);
- await takeSnapshot(
- page,
- this,
- "bid-success",
- CONSTANTS.TYPE_IMAGE.SUCCESS
- );
// sendMessage(this);
pushPrice({
bid_id: this.id,
- price: aucUserMaxBid,
+ price: this.max_price,
});
+ await takeSnapshot(
+ this.page_context,
+ this,
+ "bid-success",
+ CONSTANTS.TYPE_IMAGE.SUCCESS
+ );
+
console.log(`✅ [${this.id}] Bid placed successfully!`);
return;
}