update all bid
This commit is contained in:
parent
52167a360b
commit
41e174f545
|
|
@ -1,13 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="public/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="public/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bid System</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } });
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <a href="${bid.url}">${bid.name}</a>.`,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 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 <a href="${bid.url}">${bid.name}</a>. 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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
|
|
|||
|
|
@ -468,4 +468,68 @@ export class MailsService {
|
|||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
generateWarningEmailTemplate(title: string, messages: string[]): string {
|
||||
const timestamp = new Date().toLocaleString(); // hoặc new Date().toISOString()
|
||||
|
||||
return `
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #fffbe6; color: #333;">
|
||||
<h2 style="color: #d9534f;">⚠️ ${title}</h2>
|
||||
<ul>
|
||||
${messages.map((msg) => `<li>${msg}</li>`).join('')}
|
||||
</ul>
|
||||
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
||||
Sent at: ${timestamp}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
generateSuccessEmailTemplate(title: string, messages: string[]): string {
|
||||
const timestamp = new Date().toLocaleString(); // hoặc new Date().toISOString()
|
||||
|
||||
return `
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #e6fff2; color: #333;">
|
||||
<h2 style="color: #28a745;">✅ ${title}</h2>
|
||||
<ul>
|
||||
${messages.map((msg) => `<li>${msg}</li>`).join('')}
|
||||
</ul>
|
||||
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
||||
Sent at: ${timestamp}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
generateErrorEmailTemplate(title: string, messages: string[]): string {
|
||||
const timestamp = new Date().toLocaleString();
|
||||
|
||||
return `
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #fdecea; color: #333;">
|
||||
<h2 style="color: #c82333;">❌ ${title}</h2>
|
||||
<ul>
|
||||
${messages.map((msg) => `<li>${msg}</li>`).join('')}
|
||||
</ul>
|
||||
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
||||
Sent at: ${timestamp}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
generateInfoEmailTemplate(title: string, messages: string[]): string {
|
||||
const timestamp = new Date().toLocaleString();
|
||||
|
||||
return `
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #e7f3fe; color: #333;">
|
||||
<h2 style="color: #31708f;">ℹ️ ${title}</h2>
|
||||
<ul>
|
||||
${messages.map((msg) => `<li>${msg}</li>`).join('')}
|
||||
</ul>
|
||||
<p style="margin-top: 20px; font-size: 12px; color: #999;">
|
||||
Sent at: ${timestamp}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue