update template mail
This commit is contained in:
parent
43dcfc78bb
commit
aea4169a50
|
|
@ -17,11 +17,13 @@ export default function DeleteRowAction({
|
|||
setConfirm({
|
||||
handleOk: async () => {
|
||||
const result = await deletesBid(chooses);
|
||||
|
||||
console.log({ result });
|
||||
if (!result) return;
|
||||
onDeleted?.();
|
||||
},
|
||||
title: 'Delete',
|
||||
message: `This action will remove ${chooses.length} products.`
|
||||
title: "Delete",
|
||||
message: `This action will remove ${chooses.length} products.`,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,2 @@
|
|||
export { default as ShowHistoriesModal } from './show-histories-modal';
|
||||
export { default as ShowHistoriesBidGraysApiModal } from './show-histories-bid-grays-api-modal';
|
||||
export { default as ShowHistoriesBidPicklesApiModal } from './show-histories-bid-pickles-api-modal';
|
||||
export { default as BidModal } from './bid-modal';
|
||||
export { default as ShowHistoriesModal } from "./show-histories-modal";
|
||||
export { default as BidModal } from "./bid-modal";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import Table from "../../../lib/table/table";
|
||||
import { IColumn } from "../../../lib/table/type";
|
||||
import { formatTime } from "../../../utils";
|
||||
export interface IGraysHistoriesViewProps {
|
||||
histories: Record<string, string>[];
|
||||
}
|
||||
|
||||
export default function AllbidsHistoriesView({
|
||||
histories,
|
||||
}: IGraysHistoriesViewProps) {
|
||||
type BidHistoryEntry = {
|
||||
row_id: number;
|
||||
date: string; // ISO datetime string
|
||||
amount: number; // Số tiền đặt giá
|
||||
proxyamount: number; // Giá proxy tối đa
|
||||
bidQty: number; // Số lượng
|
||||
flashBuy: boolean; // Mua ngay
|
||||
proxyBid: boolean; // Có phải đấu giá tự động
|
||||
instantBid: boolean; // Đặt giá ngay
|
||||
userName: string; // Tên người dùng
|
||||
bidBidderID: number; // ID người đặt giá
|
||||
$$hashKey?: string; // Khóa nội bộ Angular (không cần thiết, có thể bỏ hoặc để optional)
|
||||
};
|
||||
|
||||
const columns: IColumn<BidHistoryEntry>[] = [
|
||||
{
|
||||
title: "Username",
|
||||
key: "userName",
|
||||
},
|
||||
{
|
||||
title: "Amount",
|
||||
key: "amount",
|
||||
},
|
||||
{
|
||||
title: "Proxy amount",
|
||||
key: "proxyamount",
|
||||
},
|
||||
{
|
||||
title: "Bid Qty",
|
||||
key: "bidQty",
|
||||
},
|
||||
{
|
||||
title: "Bid at",
|
||||
key: "date",
|
||||
renderRow(row) {
|
||||
return <span>{formatTime(row.date)}</span>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
withTableBorder
|
||||
withColumnBorders
|
||||
styleDefaultHead={{
|
||||
justifyContent: "flex-start",
|
||||
width: "fit-content",
|
||||
}}
|
||||
showFilter={false}
|
||||
showActions={false}
|
||||
showChooses={false}
|
||||
columns={columns}
|
||||
rowKey="row_id"
|
||||
rows={histories as unknown as BidHistoryEntry[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useMemo } from "react";
|
||||
import { extractNumber, formatTime } from "../../../utils";
|
||||
import Table from "../../../lib/table/table";
|
||||
import { IColumn } from "../../../lib/table/type";
|
||||
|
||||
export interface IGraysHistoriesViewProps {
|
||||
histories: Record<string, string>[];
|
||||
}
|
||||
|
||||
export default function GraysHistoriesView({
|
||||
histories,
|
||||
}: IGraysHistoriesViewProps) {
|
||||
type BidHistoryEntry = {
|
||||
row_id: number;
|
||||
Price: string;
|
||||
Quantity: number;
|
||||
WinningQuantity: number;
|
||||
UserShortAddress: string;
|
||||
UserInitials: string;
|
||||
OriginalDate: string;
|
||||
};
|
||||
|
||||
const columns: IColumn<BidHistoryEntry>[] = [
|
||||
{
|
||||
title: "Bidding Details",
|
||||
key: "UserInitials",
|
||||
renderRow(row) {
|
||||
return (
|
||||
<span>{`${row["UserInitials"]} - ${row["UserShortAddress"]}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Bid Time",
|
||||
key: "OriginalDate",
|
||||
renderRow(row) {
|
||||
return (
|
||||
<span>
|
||||
{formatTime(
|
||||
new Date(extractNumber(row["OriginalDate"]) || 0).toUTCString(),
|
||||
"HH:mm:ss DD/MM/YYYY"
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Bid Price",
|
||||
key: "Price",
|
||||
},
|
||||
{
|
||||
title: "Bid Qty",
|
||||
key: "Quantity",
|
||||
},
|
||||
{
|
||||
title: "Win Qty",
|
||||
key: "WinningQuantity",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
withTableBorder
|
||||
withColumnBorders
|
||||
styleDefaultHead={{
|
||||
justifyContent: "flex-start",
|
||||
width: "fit-content",
|
||||
}}
|
||||
showFilter={false}
|
||||
showActions={false}
|
||||
showChooses={false}
|
||||
columns={columns}
|
||||
rowKey="row_id"
|
||||
rows={histories as unknown as BidHistoryEntry[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useMemo } from "react";
|
||||
import { formatTime } from "../../../utils";
|
||||
import { IColumn } from "../../../lib/table/type";
|
||||
import Table from "../../../lib/table/table";
|
||||
|
||||
export interface IGraysHistoriesViewProps {
|
||||
histories: Record<string, string>[];
|
||||
}
|
||||
|
||||
export default function PicklesHistoriesView({
|
||||
histories,
|
||||
}: IGraysHistoriesViewProps) {
|
||||
// const rows = useMemo(() => {
|
||||
// return histories.map((element, index) => (
|
||||
// <Table.Tr key={index}>
|
||||
// <Table.Td>{element["bidderAnonName"]}</Table.Td>
|
||||
// <Table.Td>{element["actualBid"]}</Table.Td>
|
||||
// <Table.Td>
|
||||
// {formatTime(
|
||||
// new Date(element["bidTimeInMilliSeconds"]).toUTCString(),
|
||||
// "HH:mm:ss DD/MM/YYYY"
|
||||
// )}
|
||||
// </Table.Td>
|
||||
// </Table.Tr>
|
||||
// ));
|
||||
// }, [histories]);
|
||||
|
||||
type BidHistoryEntry = {
|
||||
row_id: number;
|
||||
bidderAnonName: string;
|
||||
actualBid: number;
|
||||
bidTimeInMilliSeconds: number;
|
||||
};
|
||||
|
||||
const columns: IColumn<BidHistoryEntry>[] = [
|
||||
{
|
||||
title: "Bidder name",
|
||||
key: "bidderAnonName",
|
||||
},
|
||||
{
|
||||
title: "Actual bid",
|
||||
key: "actualBid",
|
||||
},
|
||||
{
|
||||
title: "Time",
|
||||
key: "bidTimeInMilliSeconds",
|
||||
renderRow(row) {
|
||||
return (
|
||||
<span>
|
||||
{formatTime(
|
||||
new Date(row["bidTimeInMilliSeconds"]).toUTCString(),
|
||||
"HH:mm:ss DD/MM/YYYY"
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
striped
|
||||
highlightOnHover
|
||||
withTableBorder
|
||||
withColumnBorders
|
||||
styleDefaultHead={{
|
||||
justifyContent: "flex-start",
|
||||
width: "fit-content",
|
||||
}}
|
||||
showFilter={false}
|
||||
showActions={false}
|
||||
showChooses={false}
|
||||
columns={columns}
|
||||
rowKey="row_id"
|
||||
rows={histories as unknown as BidHistoryEntry[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { LoadingOverlay, Modal, ModalProps } from "@mantine/core";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { getDetailBidHistories } from "../../../apis/bid-histories";
|
||||
import { IBid } from "../../../system/type";
|
||||
import GraysHistoriesView from "./grays-histories-view";
|
||||
import constants from "../../../constant";
|
||||
import PicklesHistoriesView from "./pickles-histories-view";
|
||||
import AllbidsHistoriesView from "./allbids-histories-view";
|
||||
|
||||
export interface IShowHistoriesApiModalProps extends ModalProps {
|
||||
data: IBid | null;
|
||||
onUpdated?: () => void;
|
||||
}
|
||||
|
||||
export default function ShowHistoriesApiModal({
|
||||
data,
|
||||
onUpdated,
|
||||
...props
|
||||
}: IShowHistoriesApiModalProps) {
|
||||
const [histories, setHistories] = useState<Record<string, string>[]>([]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleCallApi = useCallback(async () => {
|
||||
if (!data?.lot_id) {
|
||||
setHistories([]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const response = await getDetailBidHistories(data?.lot_id);
|
||||
setLoading(false);
|
||||
|
||||
if (response.data && response.data) {
|
||||
const values = (response.data as Record<string, string>[]).map(
|
||||
(item, index) => {
|
||||
return {
|
||||
...item,
|
||||
row_id: String(index),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
setHistories(values);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
handleCallApi();
|
||||
}, [handleCallApi]);
|
||||
|
||||
const generateView = useMemo(() => {
|
||||
switch (data?.web_bid.origin_url) {
|
||||
case constants.grays:
|
||||
return <GraysHistoriesView histories={histories} />;
|
||||
case constants.pickles:
|
||||
return <PicklesHistoriesView histories={histories} />;
|
||||
case constants.allbids:
|
||||
return <AllbidsHistoriesView histories={histories} />;
|
||||
}
|
||||
}, [data?.web_bid.origin_url, histories]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="relative"
|
||||
{...props}
|
||||
size="xl"
|
||||
title={<span className="text-xl font-bold">BIDDING HISTORY</span>}
|
||||
centered
|
||||
>
|
||||
{generateView}
|
||||
<LoadingOverlay
|
||||
visible={loading}
|
||||
zIndex={1000}
|
||||
overlayProps={{ blur: 2 }}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { getDetailBidHistories } from '../../apis/bid-histories';
|
||||
import { IBid } from '../../system/type';
|
||||
import { extractNumber, formatTime } from '../../utils';
|
||||
|
||||
export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
|
||||
data: IBid | null;
|
||||
onUpdated?: () => void;
|
||||
}
|
||||
|
||||
export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...props }: IShowHistoriesBidGraysApiModalProps) {
|
||||
const [histories, setHistories] = useState<Record<string, string>[]>([]);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const rows = useMemo(() => {
|
||||
return histories.map((element, index) => (
|
||||
<Table.Tr key={index}>
|
||||
<Table.Td>{`${element['UserInitials']} - ${element['UserShortAddress']}`}</Table.Td>
|
||||
<Table.Td>{formatTime(new Date(extractNumber(element['OriginalDate']) || 0).toUTCString(), 'HH:mm:ss DD/MM/YYYY')}</Table.Td>
|
||||
<Table.Td>{`AU $${element['Price']}`}</Table.Td>
|
||||
<Table.Td>{`${element['Quantity']}`}</Table.Td>
|
||||
<Table.Td>{`${element['WinningQuantity']}`}</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
}, [histories]);
|
||||
|
||||
const handleCallApi = useCallback(async () => {
|
||||
if (!data?.lot_id) {
|
||||
setHistories([]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const response = await getDetailBidHistories(data?.lot_id);
|
||||
setLoading(false);
|
||||
|
||||
if (response.data && response.data) {
|
||||
setHistories(response.data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
handleCallApi();
|
||||
}, [handleCallApi]);
|
||||
|
||||
return (
|
||||
<Modal className="relative" {...props} size="xl" title={<span className="text-xl font-bold">BIDDING HISTORY</span>} centered>
|
||||
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Bidding Details</Table.Th>
|
||||
<Table.Th>Bid Time</Table.Th>
|
||||
<Table.Th>Bid Price</Table.Th>
|
||||
<Table.Th>Bid Qty</Table.Th>
|
||||
<Table.Th>Win Qty</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{histories.length <= 0 ? (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={5} className="text-center">
|
||||
None
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
) : (
|
||||
rows
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
|
||||
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
const constants = {
|
||||
grays: 'https://www.grays.com',
|
||||
pickles:'https://www.pickles.com.au'
|
||||
}
|
||||
const constants = {
|
||||
grays: "https://www.grays.com",
|
||||
pickles: "https://www.pickles.com.au",
|
||||
allbids: "https://www.allbids.com.au",
|
||||
};
|
||||
|
||||
export const haveHistories = [constants.grays, constants.pickles]
|
||||
export const haveHistories = [
|
||||
constants.grays,
|
||||
constants.pickles,
|
||||
constants.allbids,
|
||||
];
|
||||
|
||||
export default constants
|
||||
export default constants;
|
||||
|
|
|
|||
|
|
@ -21,22 +21,18 @@ import {
|
|||
} from "@tabler/icons-react";
|
||||
import _ from "lodash";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { deleteBid, deletesBid, getBids, toggleBid } from "../apis/bid";
|
||||
import {
|
||||
BidModal,
|
||||
ShowHistoriesBidGraysApiModal,
|
||||
ShowHistoriesBidPicklesApiModal,
|
||||
ShowHistoriesModal,
|
||||
} from "../components/bid";
|
||||
import constants, { haveHistories } from "../constant";
|
||||
import { deleteBid, getBids, toggleBid } from "../apis/bid";
|
||||
import { BidModal, ShowHistoriesModal } from "../components/bid";
|
||||
import DeleteRowAction from "../components/bid/delete-row-action";
|
||||
import { haveHistories } from "../constant";
|
||||
import Table from "../lib/table/table";
|
||||
import { IColumn, TRefTableFn } from "../lib/table/type";
|
||||
import { useChoosesStore } from "../lib/zustand/use-chooses-store";
|
||||
import { useConfirmStore } from "../lib/zustand/use-confirm";
|
||||
import { mappingStatusColors } from "../system/constants";
|
||||
import { IBid } from "../system/type";
|
||||
import { extractDomainSmart, formatTime } from "../utils";
|
||||
import DeleteRowAction from "../components/bid/delete-row-action";
|
||||
import { useChoosesStore } from "../lib/zustand/use-chooses-store";
|
||||
import ShowHistoriesApiModal from "../components/bid/show-histories-api/show-histories-api-modal";
|
||||
|
||||
export default function Bids() {
|
||||
const refTableFn: TRefTableFn<IBid> = useRef({});
|
||||
|
|
@ -47,12 +43,9 @@ export default function Bids() {
|
|||
|
||||
const { setChooses } = useChoosesStore();
|
||||
|
||||
const [openedHistories, historiesModel] = useDisclosure(false);
|
||||
const [openedHistoriesGraysApi, historiesGraysApiModel] =
|
||||
useDisclosure(false);
|
||||
const [openedHistories, historiesModal] = useDisclosure(false);
|
||||
|
||||
const [openedHistoriesPicklesApi, historiesPicklesApiModel] =
|
||||
useDisclosure(false);
|
||||
const [openedHistoriesView, openedHistoriesViewModal] = useDisclosure(false);
|
||||
const [openedBid, bidModal] = useDisclosure(false);
|
||||
|
||||
const columns: IColumn<IBid>[] = [
|
||||
|
|
@ -84,7 +77,9 @@ export default function Bids() {
|
|||
title: "Web",
|
||||
typeFilter: "none",
|
||||
renderRow(row) {
|
||||
return <span>{extractDomainSmart(row.web_bid.origin_url)}</span>;
|
||||
return (
|
||||
<span>{extractDomainSmart(row.web_bid?.origin_url) || "None"}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -238,28 +233,6 @@ export default function Bids() {
|
|||
}}
|
||||
actionsOptions={{
|
||||
showMainAction: false,
|
||||
actions: [
|
||||
{
|
||||
key: "delete",
|
||||
title: "Delete",
|
||||
callback: (data) => {
|
||||
if (!data.length) return;
|
||||
setConfirm({
|
||||
title: "Delete",
|
||||
message: `${data.length} will be delete`,
|
||||
handleOk: async () => {
|
||||
const result = await deletesBid(data);
|
||||
|
||||
if (!result) return;
|
||||
if (refTableFn.current.fetchData) {
|
||||
refTableFn.current.fetchData();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
disabled: (data) => data.length <= 0,
|
||||
},
|
||||
],
|
||||
leftActionSession: (
|
||||
<Box className="flex items-end gap-2">
|
||||
<Button
|
||||
|
|
@ -270,7 +243,11 @@ export default function Bids() {
|
|||
Add
|
||||
</Button>
|
||||
|
||||
<DeleteRowAction onDeleted={refTableFn.current?.fetchData} />
|
||||
<DeleteRowAction
|
||||
onDeleted={() => {
|
||||
refTableFn.current?.fetchData?.();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
|
|
@ -329,29 +306,17 @@ export default function Bids() {
|
|||
<Menu.Item
|
||||
onClick={() => {
|
||||
setClickData(row);
|
||||
historiesModel.open();
|
||||
historiesModal.open();
|
||||
}}
|
||||
leftSection={<IconHistory size={14} />}
|
||||
>
|
||||
Histories
|
||||
</Menu.Item>
|
||||
{haveHistories.includes(row?.web_bid.origin_url) && (
|
||||
{haveHistories.includes(row?.web_bid?.origin_url) && (
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
setClickData(row);
|
||||
switch (row.web_bid.origin_url) {
|
||||
case constants.grays: {
|
||||
historiesGraysApiModel.open();
|
||||
break;
|
||||
}
|
||||
case constants.pickles: {
|
||||
historiesPicklesApiModel.open();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
historiesGraysApiModel.open();
|
||||
}
|
||||
}
|
||||
openedHistoriesViewModal.open();
|
||||
}}
|
||||
leftSection={<IconHammer size={14} />}
|
||||
>
|
||||
|
|
@ -405,7 +370,7 @@ export default function Bids() {
|
|||
<ShowHistoriesModal
|
||||
opened={openedHistories}
|
||||
onClose={() => {
|
||||
historiesModel.close();
|
||||
historiesModal.close();
|
||||
setClickData(null);
|
||||
}}
|
||||
data={clickData}
|
||||
|
|
@ -426,28 +391,9 @@ export default function Bids() {
|
|||
}}
|
||||
data={clickData}
|
||||
/>
|
||||
{/* Grays */}
|
||||
{openedHistoriesGraysApi && (
|
||||
<ShowHistoriesBidGraysApiModal
|
||||
onUpdated={() => {
|
||||
if (refTableFn.current?.fetchData) {
|
||||
refTableFn.current.fetchData();
|
||||
}
|
||||
|
||||
setClickData(null);
|
||||
}}
|
||||
opened={openedHistoriesGraysApi}
|
||||
onClose={() => {
|
||||
historiesGraysApiModel.close();
|
||||
|
||||
setClickData(null);
|
||||
}}
|
||||
data={clickData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{openedHistoriesPicklesApi && (
|
||||
<ShowHistoriesBidPicklesApiModal
|
||||
{openedHistoriesView && (
|
||||
<ShowHistoriesApiModal
|
||||
onUpdated={() => {
|
||||
if (refTableFn.current?.fetchData) {
|
||||
refTableFn.current.fetchData();
|
||||
|
|
@ -457,7 +403,7 @@ export default function Bids() {
|
|||
}}
|
||||
opened={true}
|
||||
onClose={() => {
|
||||
historiesPicklesApiModel.close();
|
||||
openedHistoriesViewModal.close();
|
||||
setClickData(null);
|
||||
}}
|
||||
data={clickData}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"createdAt":1747812172479}
|
||||
{"createdAt":1747970028717}
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
"axios": "^1.8.3",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bull": "^4.16.5",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cookie": "^1.0.2",
|
||||
|
|
@ -4661,7 +4660,8 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
|
|
@ -4968,36 +4968,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
|
||||
"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.1.0",
|
||||
"encoding-sniffer": "^0.2.0",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"parse5": "^7.1.2",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.0.0",
|
||||
"parse5-parser-stream": "^7.1.2",
|
||||
"undici": "^6.19.5",
|
||||
"whatwg-mimetype": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
|
|
@ -5582,6 +5558,7 @@
|
|||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
|
|
@ -5598,6 +5575,7 @@
|
|||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
|
|
@ -5873,6 +5851,7 @@
|
|||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
|
|
@ -5892,13 +5871,15 @@
|
|||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "BSD-2-Clause"
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
|
|
@ -5914,6 +5895,7 @@
|
|||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
|
|
@ -6100,31 +6082,6 @@
|
|||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding-sniffer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
|
||||
"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"whatwg-encoding": "^3.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding-sniffer/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -6211,6 +6168,7 @@
|
|||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
|
|
@ -7844,6 +7802,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
|
|
@ -10921,6 +10880,7 @@
|
|||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
|
|
@ -11224,6 +11184,7 @@
|
|||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
|
|
@ -11236,6 +11197,7 @@
|
|||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.3",
|
||||
"parse5": "^7.0.0"
|
||||
|
|
@ -11244,23 +11206,12 @@
|
|||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-parser-stream": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5/node_modules/entities": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
|
||||
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
|
|
@ -14245,15 +14196,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
|
|
@ -14726,39 +14668,6 @@
|
|||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
"axios": "^1.8.3",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bull": "^4.16.5",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cookie": "^1.0.2",
|
||||
|
|
|
|||
|
|
@ -1,59 +1,51 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
import AppResponse from 'src/response/app-response';
|
||||
import { Bid } from '../entities/bid.entity';
|
||||
import { BidsService } from '../services/bids.service';
|
||||
|
||||
@Injectable()
|
||||
export class GraysApi {
|
||||
|
||||
|
||||
constructor(private readonly bidsService: BidsService){}
|
||||
|
||||
|
||||
async getHistoriesBid(lot_id: Bid['lot_id']) {
|
||||
|
||||
|
||||
const bid= await this.bidsService.bidsRepo.findOne({where: {lot_id, }, relations: {web_bid: true}})
|
||||
|
||||
|
||||
try {
|
||||
|
||||
switch(bid.web_bid.origin_url){
|
||||
|
||||
// GRAYS
|
||||
case 'https://www.grays.com': {
|
||||
const response = await axios({
|
||||
url: `https://www.grays.com/api/LotInfo/GetBiddingHistory?lotId=${lot_id}¤cyCode=AUD`,
|
||||
});
|
||||
|
||||
if (response.data && response.data?.Bids) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([])
|
||||
}
|
||||
|
||||
// PICKLES
|
||||
case 'https://www.pickles.com.au': {
|
||||
|
||||
const response = await axios({
|
||||
url: `https://www.pickles.com.au/PWR-Web/services/api/bidHistoryService/bidHistory?item=${lot_id}`,
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([])
|
||||
}
|
||||
default:
|
||||
return AppResponse.toResponse([])
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
import AppResponse from 'src/response/app-response';
|
||||
import { Bid } from '../entities/bid.entity';
|
||||
import { BidsService } from '../services/bids.service';
|
||||
|
||||
@Injectable()
|
||||
export class AllBidsApi {
|
||||
constructor(private readonly bidsService: BidsService) {}
|
||||
|
||||
async getHistoriesBid(lot_id: Bid['lot_id']) {
|
||||
const bid = await this.bidsService.bidsRepo.findOne({
|
||||
where: { lot_id },
|
||||
relations: { web_bid: true },
|
||||
});
|
||||
|
||||
try {
|
||||
switch (bid.web_bid.origin_url) {
|
||||
// GRAYS
|
||||
case 'https://www.grays.com': {
|
||||
const response = await axios({
|
||||
url: `https://www.grays.com/api/LotInfo/GetBiddingHistory?lotId=${lot_id}¤cyCode=AUD`,
|
||||
});
|
||||
|
||||
if (response.data && response.data?.Bids) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
|
||||
// PICKLES
|
||||
case 'https://www.pickles.com.au': {
|
||||
const response = await axios({
|
||||
url: `https://www.pickles.com.au/PWR-Web/services/api/bidHistoryService/bidHistory?item=${lot_id}`,
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
default:
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
} catch (error) {
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
import AppResponse from 'src/response/app-response';
|
||||
import { Bid } from '../entities/bid.entity';
|
||||
import { BidsService } from '../services/bids.service';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class AuctionHistoresApi {
|
||||
constructor(private readonly bidsService: BidsService) {}
|
||||
|
||||
async getHistoriesBid(lot_id: Bid['lot_id']) {
|
||||
const bid = await this.bidsService.bidsRepo.findOne({
|
||||
where: { lot_id },
|
||||
relations: { web_bid: true, metadata: true },
|
||||
});
|
||||
|
||||
try {
|
||||
switch (bid.web_bid.origin_url) {
|
||||
// GRAYS
|
||||
case 'https://www.grays.com': {
|
||||
const response = await axios({
|
||||
url: `https://www.grays.com/api/LotInfo/GetBiddingHistory?lotId=${lot_id}¤cyCode=AUD`,
|
||||
});
|
||||
|
||||
if (response.data && response.data?.Bids) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
|
||||
// PICKLES
|
||||
case 'https://www.pickles.com.au': {
|
||||
const response = await axios({
|
||||
url: `https://www.pickles.com.au/PWR-Web/services/api/bidHistoryService/bidHistory?item=${lot_id}`,
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
return AppResponse.toResponse(response.data.Bids);
|
||||
}
|
||||
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
// ALLBIDS
|
||||
case 'https://www.allbids.com.au': {
|
||||
const data = bid.metadata.find(
|
||||
(meta) => meta.key_name === 'competor_histories',
|
||||
)?.value;
|
||||
|
||||
const sorted = _.orderBy(data, ['amount'], ['desc']);
|
||||
|
||||
return AppResponse.toResponse(sorted || []);
|
||||
}
|
||||
default:
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
} catch (error) {
|
||||
return AppResponse.toResponse([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||
import { AdminsModule } from '../admins/admins.module';
|
||||
import { NotificationModule } from '../notification/notification.module';
|
||||
import { BotTelegramApi } from './apis/bot-telegram.api';
|
||||
import { GraysApi } from './apis/grays.api';
|
||||
import { AuctionHistoresApi } from './apis/auction-histories.api';
|
||||
import { AdminBidHistoriesController } from './controllers/admin/admin-bid-histories.controller';
|
||||
import { AdminBidsController } from './controllers/admin/admin-bids.controller';
|
||||
import { AdminOutBidLogsController } from './controllers/admin/admin-out-bid-logs.controller';
|
||||
|
|
@ -31,6 +31,8 @@ import { TasksService } from './services/tasks.servise';
|
|||
import { ConfigsService } from './services/configs.service';
|
||||
import { Config } from './entities/configs.entity';
|
||||
import { AdminConfigsController } from './controllers/admin/admin-configs.controller';
|
||||
import { BidMetadatasService } from './services/bid-metadatas.service';
|
||||
import { BidMetadata } from './entities/bid-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -41,6 +43,7 @@ import { AdminConfigsController } from './controllers/admin/admin-configs.contro
|
|||
WebBid,
|
||||
SendMessageHistory,
|
||||
Config,
|
||||
BidMetadata,
|
||||
]),
|
||||
// AuthModule,
|
||||
AdminsModule,
|
||||
|
|
@ -66,12 +69,13 @@ import { AdminConfigsController } from './controllers/admin/admin-configs.contro
|
|||
OutBidLogsService,
|
||||
WebBidsService,
|
||||
BotTelegramApi,
|
||||
GraysApi,
|
||||
AuctionHistoresApi,
|
||||
SendMessageHistoriesService,
|
||||
ImapService,
|
||||
DashboardService,
|
||||
TasksService,
|
||||
ConfigsService,
|
||||
BidMetadatasService,
|
||||
],
|
||||
exports: [
|
||||
BotTelegramApi,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { CreateBidDto } from '../../dto/bid/create-bid.dto';
|
|||
import { BidHistoriesService } from '../../services/bid-histories.service';
|
||||
import { CreateBidHistoryDto } from '../../dto/bid-history/create-bid-history.dto';
|
||||
import { Bid } from '../../entities/bid.entity';
|
||||
import { GraysApi } from '../../apis/grays.api';
|
||||
import { AuctionHistoresApi } from '../../apis/auction-histories.api';
|
||||
|
||||
@Controller('admin/bid-histories')
|
||||
export class AdminBidHistoriesController {
|
||||
constructor(
|
||||
private readonly bidHistoriesService: BidHistoriesService,
|
||||
private readonly graysApi: GraysApi,
|
||||
private readonly auctionHistoresApi: AuctionHistoresApi,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
|
|
@ -20,6 +20,6 @@ export class AdminBidHistoriesController {
|
|||
|
||||
@Get('detail/:lot_id')
|
||||
async getBidHistories(@Param('lot_id') lot_id: Bid['lot_id']) {
|
||||
return await this.graysApi.getHistoriesBid(lot_id);
|
||||
return await this.auctionHistoresApi.getHistoriesBid(lot_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
import { IsNumber, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class ClientUpdateBidDto {
|
||||
@IsString()
|
||||
|
|
@ -24,4 +24,8 @@ export class ClientUpdateBidDto {
|
|||
@IsNumber()
|
||||
@IsOptional()
|
||||
reserve_price: number;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import {
|
||||
Column,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Bid } from './bid.entity';
|
||||
import { Timestamp } from './timestamp';
|
||||
|
||||
@Entity('bid_metadata')
|
||||
@Unique(['key_name', 'bid'])
|
||||
export class BidMetadata extends Timestamp {
|
||||
@PrimaryGeneratedColumn('increment')
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
key_name: string;
|
||||
|
||||
@Column({ type: 'json' })
|
||||
value: string;
|
||||
|
||||
@ManyToOne(() => Bid, (bid) => bid.metadata, { onDelete: 'CASCADE' })
|
||||
bid: Bid;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { Timestamp } from './timestamp';
|
|||
import { BidHistory } from './bid-history.entity';
|
||||
import { WebBid } from './wed-bid.entity';
|
||||
import { SendMessageHistory } from './send-message-histories.entity';
|
||||
import { BidMetadata } from './bid-metadata.entity';
|
||||
|
||||
@Entity('bids')
|
||||
export class Bid extends Timestamp {
|
||||
|
|
@ -69,4 +70,7 @@ export class Bid extends Timestamp {
|
|||
|
||||
@ManyToOne(() => WebBid, (web) => web.children, { onDelete: 'CASCADE' })
|
||||
web_bid: WebBid;
|
||||
|
||||
@OneToMany(() => BidMetadata, (metadata) => metadata.bid)
|
||||
metadata: BidMetadata[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BidMetadata } from '../entities/bid-metadata.entity';
|
||||
import { Bid } from '../entities/bid.entity';
|
||||
|
||||
@Injectable()
|
||||
export class BidMetadatasService {
|
||||
constructor(
|
||||
@InjectRepository(BidMetadata)
|
||||
readonly bidMetadataRepo: Repository<BidMetadata>,
|
||||
) {}
|
||||
|
||||
async upsert(data: Record<string, any>, bid: Bid) {
|
||||
const existingMetadata = await this.bidMetadataRepo.find({
|
||||
where: { bid: { id: bid.id } },
|
||||
});
|
||||
|
||||
const existingMap = new Map(
|
||||
existingMetadata.map((item) => [item.key_name, item]),
|
||||
);
|
||||
|
||||
const toSave: BidMetadata[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const existing = existingMap.get(key);
|
||||
if (existing) {
|
||||
existing.value = value;
|
||||
toSave.push(existing);
|
||||
} else {
|
||||
toSave.push(
|
||||
this.bidMetadataRepo.create({
|
||||
key_name: key,
|
||||
value,
|
||||
bid: { id: bid.id },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.bidMetadataRepo.save(toSave);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ import { ImageCompressionPipe } from '../pipes/image-compression-pipe';
|
|||
import { Constant } from '../utils/constant';
|
||||
import { Event } from '../utils/events';
|
||||
import { WebBidsService } from './web-bids.service';
|
||||
import { BidMetadatasService } from './bid-metadatas.service';
|
||||
|
||||
@Injectable()
|
||||
export class BidsService {
|
||||
|
|
@ -41,8 +42,9 @@ export class BidsService {
|
|||
@InjectRepository(BidHistory)
|
||||
readonly bidHistoriesRepo: Repository<BidHistory>,
|
||||
private readonly webBidsService: WebBidsService,
|
||||
private eventEmitter: EventEmitter2,
|
||||
private notificationService: NotificationService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly bidMetadatasService: BidMetadatasService,
|
||||
) {}
|
||||
|
||||
async index(query: PaginateQuery) {
|
||||
|
|
@ -208,12 +210,12 @@ export class BidsService {
|
|||
|
||||
async clientUpdate(
|
||||
id: Bid['id'],
|
||||
{ close_time, model, ...data }: ClientUpdateBidDto, // Nhận dữ liệu cập nhật
|
||||
{ close_time, model, metadata, ...data }: ClientUpdateBidDto, // Nhận dữ liệu cập nhật
|
||||
) {
|
||||
// Tìm kiếm phiên đấu giá trong database theo id
|
||||
const bid = await this.bidsRepo.findOne({
|
||||
where: { id },
|
||||
relations: { histories: true, web_bid: true },
|
||||
relations: { histories: true, web_bid: true, metadata: true },
|
||||
order: {
|
||||
histories: {
|
||||
price: 'DESC',
|
||||
|
|
@ -288,6 +290,10 @@ export class BidsService {
|
|||
updated_at: new Date(), // Cập nhật timestamp
|
||||
});
|
||||
|
||||
if (metadata) {
|
||||
await this.bidMetadatasService.upsert(metadata, bid);
|
||||
}
|
||||
|
||||
// Phát sự kiện cập nhật toàn bộ danh sách đấu giá
|
||||
this.emitAllBidEvent();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { Queue } from 'bull';
|
|||
export class MailsService {
|
||||
constructor(
|
||||
private readonly mailerService: MailerService,
|
||||
|
||||
@InjectQueue('mail-queue') private mailQueue: Queue,
|
||||
) {}
|
||||
|
||||
|
|
@ -52,6 +53,8 @@ export class MailsService {
|
|||
}
|
||||
|
||||
generateProductTableHTML(products: ScrapItem[]): string {
|
||||
const from = process.env.MAIL_USER || 'no-reply@example.com';
|
||||
|
||||
if (!products.length) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -64,6 +67,7 @@ export class MailsService {
|
|||
<body style="font-family: sans-serif; background: #f8f9fa; padding: 20px;">
|
||||
<h2 style="text-align: center; color: #333;">Product Listing</h2>
|
||||
<p style="text-align: center; color: #666;">No matching products found for your keywords today.</p>
|
||||
<p style="text-align: center; color: #999; font-size: 12px; margin-top: 40px;">From: ${from}</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
@ -109,6 +113,7 @@ export class MailsService {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p style="text-align: center; color: #999; font-size: 12px; margin-top: 40px;">From: ${from}</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
@ -122,6 +127,7 @@ export class MailsService {
|
|||
const max = `$${bid.max_price}`;
|
||||
const submitted = `$${bid.max_price}`;
|
||||
const nextBid = bid.max_price + bid.plus_price;
|
||||
const from = process.env.MAIL_USER || 'no-reply@example.com';
|
||||
|
||||
const cardStyle = `
|
||||
max-width: 600px;
|
||||
|
|
@ -146,15 +152,16 @@ export class MailsService {
|
|||
switch (bid.status) {
|
||||
case 'biding':
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#2c7a7b')}">✅ Auto Bid Started</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('End time', endTime)}
|
||||
${renderRow('Competitor', competitor)}
|
||||
${renderRow('Bid submitted', submitted)}
|
||||
</div>
|
||||
`;
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#2c7a7b')}">✅ Auto Bid Started</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('End time', endTime)}
|
||||
${renderRow('Competitor', competitor)}
|
||||
${renderRow('Bid submitted', submitted)}
|
||||
${renderRow('From', from)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
case 'out-bid': {
|
||||
const overLimit = bid.current_price >= nextBid;
|
||||
|
|
@ -163,59 +170,64 @@ export class MailsService {
|
|||
|
||||
if (isTimeReached(bid.close_time)) {
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#718096')}">⏳ Auction Ended</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('End time', endTime)}
|
||||
${renderRow('Final price', competitor)}
|
||||
</div>
|
||||
`;
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#718096')}">⏳ Auction Ended</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('End time', endTime)}
|
||||
${renderRow('Final price', competitor)}
|
||||
${renderRow('From', from)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (overLimit || belowReserve) {
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#dd6b20')}">⚠️ Outbid (${timeExtended})</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Competitor', competitor)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('Next bid at', `$${nextBid}`)}
|
||||
${renderRow('End time', endTime)}
|
||||
<p style="color:#c05621; font-weight: 600;">⚠️ Current bid exceeds your max bid.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#e53e3e')}">🛑 Auction Canceled (${timeExtended})</h2>
|
||||
<h2 style="${headerStyle('#dd6b20')}">⚠️ Outbid (${timeExtended})</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Competitor', competitor)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('Next bid at', `$${nextBid}`)}
|
||||
${renderRow('End time', endTime)}
|
||||
<p style="color:#9b2c2c; font-weight: 600;">🛑 Auction has been canceled.</p>
|
||||
${renderRow('From', from)}
|
||||
<p style="color:#c05621; font-weight: 600;">⚠️ Current bid exceeds your max bid.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#e53e3e')}">🛑 Auction Canceled (${timeExtended})</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Competitor', competitor)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('Next bid at', `$${nextBid}`)}
|
||||
${renderRow('End time', endTime)}
|
||||
${renderRow('From', from)}
|
||||
<p style="color:#9b2c2c; font-weight: 600;">🛑 Auction has been canceled.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
case 'win-bid':
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#2b6cb0')}">🎉 You Won!</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Price won', `$${bid.current_price}`)}
|
||||
${renderRow('Max', max)}
|
||||
</div>
|
||||
`;
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#2b6cb0')}">🎉 You Won!</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('Price won', `$${bid.current_price}`)}
|
||||
${renderRow('Max', max)}
|
||||
${renderRow('From', from)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
default:
|
||||
return `
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#718096')}">❓ Unknown Status</h2>
|
||||
${renderRow('Title', title)}
|
||||
</div>
|
||||
`;
|
||||
<div style="${cardStyle}">
|
||||
<h2 style="${headerStyle('#718096')}">❓ Unknown Status</h2>
|
||||
${renderRow('Title', title)}
|
||||
${renderRow('From', from)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +239,7 @@ export class MailsService {
|
|||
const max = `$${bid.max_price}`;
|
||||
const submitted = `$${bid.max_price}`;
|
||||
const maxReached = bid.max_price <= bid.max_price;
|
||||
const from = process.env.MAIL_USER || 'no-reply@example.com';
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -300,6 +313,10 @@ export class MailsService {
|
|||
<th>End Time</th>
|
||||
<td>${endTime}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>From</th>
|
||||
<td>${from}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||
import { Cron } from '@nestjs/schedule';
|
||||
import * as moment from 'moment';
|
||||
import { Between } from 'typeorm';
|
||||
import { ScrapConfigsService } from './scrap-config.service';
|
||||
import { ScrapItemsService } from './scrap-item-config.service';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -87,7 +86,7 @@ export class TasksService {
|
|||
}
|
||||
}
|
||||
|
||||
@Cron('0 2 * * *')
|
||||
@Cron('58 5 * * *')
|
||||
async handleScraps() {
|
||||
const processName = 'scrape-data-keyword';
|
||||
await this.runProcessAndSendReport(processName);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,48 @@ export class AllbidsProductBid extends ProductBid {
|
|||
}
|
||||
}
|
||||
|
||||
async getHistoriesData() {
|
||||
if (!this.page_context) return;
|
||||
try {
|
||||
// Chờ cho Angular load (có thể tùy chỉnh thời gian nếu cần)
|
||||
await this.page_context.waitForFunction(
|
||||
() => window.angular !== undefined
|
||||
);
|
||||
|
||||
const historiesData = await this.page_context.evaluate(() => {
|
||||
let data = null;
|
||||
const elements = document.querySelectorAll(".ng-scope");
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
try {
|
||||
const scope = angular.element(elements[i]).scope();
|
||||
if (scope?.bidHistory) {
|
||||
data = scope.bidHistory;
|
||||
break;
|
||||
}
|
||||
|
||||
// Thử lấy từ $parent nếu không thấy
|
||||
if (scope?.$parent?.bidHistory) {
|
||||
data = scope.$parent.bidHistory;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Angular element có thể lỗi nếu phần tử không hợp lệ
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
return historiesData;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[${this.id}] Error in waitForApiResponse: ${error?.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleUpdateBid({
|
||||
lot_id,
|
||||
close_time,
|
||||
|
|
@ -66,6 +108,7 @@ export class AllbidsProductBid extends ProductBid {
|
|||
current_price,
|
||||
reserve_price,
|
||||
model,
|
||||
metadata,
|
||||
}) {
|
||||
const response = await updateBid(this.id, {
|
||||
lot_id,
|
||||
|
|
@ -74,6 +117,7 @@ export class AllbidsProductBid extends ProductBid {
|
|||
current_price,
|
||||
reserve_price: Number(reserve_price) || 0,
|
||||
model,
|
||||
metadata,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
|
|
@ -122,6 +166,8 @@ export class AllbidsProductBid extends ProductBid {
|
|||
// 📌 Chờ phản hồi API từ trang, tối đa 10 giây
|
||||
const result = await this.waitForApiResponse();
|
||||
|
||||
const historiesData = await this.getHistoriesData();
|
||||
|
||||
// 📌 Nếu không có dữ liệu trả về thì dừng
|
||||
if (!result) {
|
||||
console.log(`⚠️ [${this.id}] No valid data received, skipping update.`);
|
||||
|
|
@ -140,6 +186,9 @@ export class AllbidsProductBid extends ProductBid {
|
|||
: null,
|
||||
// close_time: close_time && !this.close_time ? String(close_time) : null, // test
|
||||
name: result?.aucTitle || null,
|
||||
metadata: {
|
||||
competor_histories: historiesData,
|
||||
},
|
||||
},
|
||||
["close_time"]
|
||||
);
|
||||
|
|
@ -233,12 +282,10 @@ export class AllbidsProductBid extends ProductBid {
|
|||
|
||||
const data = await this.submitBid();
|
||||
|
||||
console.log({ data });
|
||||
|
||||
await this.page_context.reload({ waitUntil: "networkidle0" });
|
||||
|
||||
const { aucUserMaxBid } = await this.waitForApiResponse();
|
||||
console.log(`📡 [${this.id}] API Response received:`, lotData);
|
||||
console.log(`📡 [${this.id}] API Response received:`, { aucUserMaxBid });
|
||||
|
||||
// 📌 Kiểm tra trạng thái đấu giá từ API
|
||||
if (aucUserMaxBid == this.max_price) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue